diff --git a/.cursorrules b/.cursorrules index 61a306ff0..a40eec787 100644 --- a/.cursorrules +++ b/.cursorrules @@ -1,4 +1,7 @@ +You are an expert in TypeScript, Node.js, Remix, React, Shadcn UI and Tailwind. + Code Style and Structure: + - Write concise, technical TypeScript code with accurate examples - Use functional and declarative programming patterns; avoid classes - Prefer iteration and modularization over code duplication @@ -6,20 +9,25 @@ Code Style and Structure: - Structure files: exported component, subcomponents, helpers, static content, types Naming Conventions: + - Use lowercase with dashes for directories (e.g., components/auth-wizard) - Favor named exports for components TypeScript Usage: -- Use TypeScript for all code; prefer interfaces over types -- Avoid enums; use maps instead + +- Use TypeScript for all code; prefer types over interfaces - Use functional components with TypeScript interfaces Syntax and Formatting: -- Use the "function" keyword for pure functions + +- Create functions using `const fn = () => {}` - Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements - Use declarative JSX +- Never use 'use client' +- Never use 1 line if statements Error Handling and Validation: + - Prioritize error handling: handle errors and edge cases early - Use early returns and guard clauses - Implement proper error logging and user-friendly messages @@ -28,21 +36,40 @@ Error Handling and Validation: - Use error boundaries for unexpected errors UI and Styling: + - Use Shadcn UI, Radix, and Tailwind Aria for components and styling - Implement responsive design with Tailwind CSS; use a mobile-first approach +- When using Lucide icons, prefer the longhand names, for example HomeIcon instead of Home -Performance Optimization: -- Minimize 'use client', 'useEffect', and 'setState'; favor React Server Components (RSC) -- Wrap client components in Suspense with fallback -- Use dynamic loading for non-critical components -- Optimize images: use WebP format, include size data, implement lazy loading +React forms -Key Conventions: -- Use 'nuqs' for URL search parameter state management -- Optimize Web Vitals (LCP, CLS, FID) -- Limit 'use client': - - Favor server components and Next.js SSR - - Use only for Web API access in small components - - Avoid for data fetching or state management +- Use zod for form validation react-hook-form for forms +- Look at TeamCreateDialog.tsx as an example of form usage +- Use
elements, and also wrap the contents of form in a fieldset which should have the :disabled attribute when the form is loading -Follow Next.js docs for Data Fetching, Rendering, and Routing \ No newline at end of file +TRPC Specifics + +- Every route should be in it's own file, example routers/teams/create-team.ts +- Every route should have a types file associated with it, example routers/teams/create-team.types.ts. These files should have the OpenAPI meta, and request/response zod schemas +- The request/response schemas should be named like Z[RouteName]RequestSchema and Z[RouteName]ResponseSchema +- Use create-team.ts and create-team.types.ts as an example when creating new routes. +- When creating the OpenAPI meta, only use GET and POST requests, do not use any other REST methods +- Deconstruct the input argument on it's one line of code. + +Toast usage + +- Use the t`string` macro from @lingui/react/macro to display toast messages + +Remix/ReactRouter Usage + +- Use (params: Route.Params) to get the params from the route +- Use (loaderData: Route.LoaderData) to get the loader data from the route +- When using loaderdata, deconstruct the data you need from the loader data inside the function body +- Do not use json() to return data, directly return the data + +Translations + +- Use string to display translations in jsx code, this should be imported from @lingui/react/macro +- Use the t`string` macro from @lingui/react/macro to display translations in typescript code +- t should be imported as const { t } = useLingui() where useLingui is imported from @lingui/react/macro +- String in constants should be using the t`string` macro diff --git a/apps/remix/app/components/dialogs/claim-create-dialog.tsx b/apps/remix/app/components/dialogs/claim-create-dialog.tsx new file mode 100644 index 000000000..c3564e7e0 --- /dev/null +++ b/apps/remix/app/components/dialogs/claim-create-dialog.tsx @@ -0,0 +1,90 @@ +import { useState } from 'react'; + +import { Trans, useLingui } from '@lingui/react/macro'; +import type { z } from 'zod'; + +import { generateDefaultSubscriptionClaim } from '@documenso/lib/utils/organisations-claims'; +import { trpc } from '@documenso/trpc/react'; +import type { ZCreateSubscriptionClaimRequestSchema } from '@documenso/trpc/server/admin-router/create-subscription-claim.types'; +import { Button } from '@documenso/ui/primitives/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@documenso/ui/primitives/dialog'; +import { useToast } from '@documenso/ui/primitives/use-toast'; + +import { SubscriptionClaimForm } from '../forms/subscription-claim-form'; + +export type CreateClaimFormValues = z.infer; + +export const ClaimCreateDialog = () => { + const { t } = useLingui(); + const { toast } = useToast(); + + const [open, setOpen] = useState(false); + + const { mutateAsync: createClaim, isPending } = trpc.admin.claims.create.useMutation({ + onSuccess: () => { + toast({ + title: t`Subscription claim created successfully.`, + }); + + setOpen(false); + }, + onError: () => { + toast({ + title: t`Failed to create subscription claim.`, + variant: 'destructive', + }); + }, + }); + + return ( + + e.stopPropagation()} asChild={true}> + + + + + + + Create Subscription Claim + + + Fill in the details to create a new subscription claim. + + + + + + + + + } + /> + + + ); +}; diff --git a/apps/remix/app/components/dialogs/claim-delete-dialog.tsx b/apps/remix/app/components/dialogs/claim-delete-dialog.tsx new file mode 100644 index 000000000..61124fa9d --- /dev/null +++ b/apps/remix/app/components/dialogs/claim-delete-dialog.tsx @@ -0,0 +1,96 @@ +import { useState } from 'react'; + +import { Trans, useLingui } from '@lingui/react/macro'; + +import { trpc } from '@documenso/trpc/react'; +import { Alert, AlertDescription } from '@documenso/ui/primitives/alert'; +import { Button } from '@documenso/ui/primitives/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@documenso/ui/primitives/dialog'; +import { useToast } from '@documenso/ui/primitives/use-toast'; + +export type ClaimDeleteDialogProps = { + claimId: string; + claimName: string; + claimLocked: boolean; + trigger: React.ReactNode; +}; + +export const ClaimDeleteDialog = ({ + claimId, + claimName, + claimLocked, + trigger, +}: ClaimDeleteDialogProps) => { + const { t } = useLingui(); + const { toast } = useToast(); + + const [open, setOpen] = useState(false); + + const { mutateAsync: deleteClaim, isPending } = trpc.admin.claims.delete.useMutation({ + onSuccess: () => { + toast({ + title: t`Subscription claim deleted successfully.`, + }); + + setOpen(false); + }, + onError: (err) => { + console.error(err); + + toast({ + title: t`Failed to delete subscription claim.`, + variant: 'destructive', + }); + }, + }); + + return ( + !isPending && setOpen(value)}> + e.stopPropagation()}> + {trigger} + + + + + + Delete Subscription Claim + + + Are you sure you want to delete the following claim? + + + + + + {claimLocked ? This claim is locked and cannot be deleted. : claimName} + + + + + + + {!claimLocked && ( + + )} + + + + ); +}; diff --git a/apps/remix/app/components/dialogs/claim-update-dialog.tsx b/apps/remix/app/components/dialogs/claim-update-dialog.tsx new file mode 100644 index 000000000..539343d40 --- /dev/null +++ b/apps/remix/app/components/dialogs/claim-update-dialog.tsx @@ -0,0 +1,92 @@ +import { useState } from 'react'; + +import { Trans, useLingui } from '@lingui/react/macro'; + +import { trpc } from '@documenso/trpc/react'; +import type { TFindSubscriptionClaimsResponse } from '@documenso/trpc/server/admin-router/find-subscription-claims.types'; +import { Button } from '@documenso/ui/primitives/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@documenso/ui/primitives/dialog'; +import { useToast } from '@documenso/ui/primitives/use-toast'; + +import { SubscriptionClaimForm } from '../forms/subscription-claim-form'; + +export type ClaimUpdateDialogProps = { + claim: TFindSubscriptionClaimsResponse['data'][number]; + trigger: React.ReactNode; +}; + +export const ClaimUpdateDialog = ({ claim, trigger }: ClaimUpdateDialogProps) => { + const { t } = useLingui(); + const { toast } = useToast(); + + const [open, setOpen] = useState(false); + + const { mutateAsync: updateClaim, isPending } = trpc.admin.claims.update.useMutation({ + onSuccess: () => { + toast({ + title: t`Subscription claim updated successfully.`, + }); + + setOpen(false); + }, + onError: () => { + toast({ + title: t`Failed to update subscription claim.`, + variant: 'destructive', + }); + }, + }); + + return ( + + e.stopPropagation()}> + {trigger} + + + + + + Update Subscription Claim + + + Modify the details of the subscription claim. + + + + + await updateClaim({ + id: claim.id, + data, + }) + } + formSubmitTrigger={ + + + + + + } + /> + + + ); +}; diff --git a/apps/remix/app/components/dialogs/organisation-create-dialog.tsx b/apps/remix/app/components/dialogs/organisation-create-dialog.tsx index 30c6d6a3f..2b85528fe 100644 --- a/apps/remix/app/components/dialogs/organisation-create-dialog.tsx +++ b/apps/remix/app/components/dialogs/organisation-create-dialog.tsx @@ -1,18 +1,25 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { zodResolver } from '@hookform/resolvers/zod'; -import { msg } from '@lingui/core/macro'; -import { useLingui } from '@lingui/react'; +import { useLingui } from '@lingui/react/macro'; import { Trans } from '@lingui/react/macro'; import type * as DialogPrimitive from '@radix-ui/react-dialog'; +import { ExternalLinkIcon } from 'lucide-react'; import { useForm } from 'react-hook-form'; -import { useNavigate } from 'react-router'; +import { Link, useSearchParams } from 'react-router'; +import { match } from 'ts-pattern'; import type { z } from 'zod'; -import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; -import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; +import type { InternalClaimPlans } from '@documenso/ee/server-only/stripe/get-internal-claim-plans'; +import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params'; +import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app'; +import { AppError } from '@documenso/lib/errors/app-error'; +import { INTERNAL_CLAIM_ID } from '@documenso/lib/types/subscription'; +import { parseMessageDescriptorMacro } from '@documenso/lib/utils/i18n'; import { trpc } from '@documenso/trpc/react'; import { ZCreateOrganisationRequestSchema } from '@documenso/trpc/server/organisation-router/create-organisation.types'; +import { cn } from '@documenso/ui/lib/utils'; +import { Badge } from '@documenso/ui/primitives/badge'; import { Button } from '@documenso/ui/primitives/button'; import { Dialog, @@ -32,6 +39,8 @@ import { FormMessage, } from '@documenso/ui/primitives/form/form'; import { Input } from '@documenso/ui/primitives/input'; +import { SpinnerBox } from '@documenso/ui/primitives/spinner'; +import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs'; import { useToast } from '@documenso/ui/primitives/use-toast'; export type OrganisationCreateDialogProps = { @@ -40,16 +49,24 @@ export type OrganisationCreateDialogProps = { const ZCreateOrganisationFormSchema = ZCreateOrganisationRequestSchema.pick({ name: true, - url: true, }); type TCreateOrganisationFormSchema = z.infer; export const OrganisationCreateDialog = ({ trigger, ...props }: OrganisationCreateDialogProps) => { - const { _ } = useLingui(); + const { t } = useLingui(); const { toast } = useToast(); - const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const updateSearchParams = useUpdateSearchParams(); + + const actionSearchParam = searchParams?.get('action'); + + const [step, setStep] = useState<'billing' | 'create'>( + IS_BILLING_ENABLED() ? 'billing' : 'create', + ); + + const [selectedPriceId, setSelectedPriceId] = useState(''); const [open, setOpen] = useState(false); @@ -57,56 +74,50 @@ export const OrganisationCreateDialog = ({ trigger, ...props }: OrganisationCrea resolver: zodResolver(ZCreateOrganisationFormSchema), defaultValues: { name: '', - url: '', }, }); const { mutateAsync: createOrganisation } = trpc.organisation.create.useMutation(); - const onFormSubmit = async ({ name, url }: TCreateOrganisationFormSchema) => { + const { data: plansData } = trpc.billing.plans.get.useQuery(); + + const onFormSubmit = async ({ name }: TCreateOrganisationFormSchema) => { try { const response = await createOrganisation({ name, - url, + priceId: selectedPriceId, }); + if (response.paymentRequired) { + window.open(response.checkoutUrl, '_blank'); + } + setOpen(false); - // if (response.paymentRequired) { - // await navigate(`/settings/teams?tab=pending&checkout=${response.pendingTeamId}`); - // return; - // } - toast({ - title: _(msg`Success`), - description: _(msg`Your organisation has been created.`), + title: t`Success`, + description: t`Your organisation has been created.`, duration: 5000, }); } catch (err) { const error = AppError.parseError(err); - if (error.code === AppErrorCode.ALREADY_EXISTS) { - form.setError('url', { - type: 'manual', - message: _(msg`This URL is already in use.`), - }); - - return; - } + console.error(error); toast({ - title: _(msg`An unknown error occurred`), - description: _( - msg`We encountered an unknown error while attempting to create a organisation. Please try again later.`, - ), + title: t`An unknown error occurred`, + description: t`We encountered an unknown error while attempting to create a organisation. Please try again later.`, variant: 'destructive', }); } }; - const mapTextToUrl = (text: string) => { - return text.toLowerCase().replace(/\s+/g, '-'); - }; + useEffect(() => { + if (actionSearchParam === 'add-organisation') { + setOpen(true); + updateSearchParams({ action: null }); + } + }, [actionSearchParam, open]); useEffect(() => { form.reset(); @@ -127,95 +138,265 @@ export const OrganisationCreateDialog = ({ trigger, ...props }: OrganisationCrea - - - Create organisation - + {match(step) + .with('billing', () => ( + <> + + + Select a plan + - - Create an organisation to collaborate with teams - - - - - -
- ( - - - Organisation Name - - - { - const oldGeneratedUrl = mapTextToUrl(field.value); - const newGeneratedUrl = mapTextToUrl(event.target.value); - - const urlField = form.getValues('url'); - if (urlField === oldGeneratedUrl) { - form.setValue('url', newGeneratedUrl); - } - - field.onChange(event); - }} - /> - - - + + Select a plan to continue + + +
+ {plansData ? ( + + ) : ( + )} - /> - ( - - - Organisation URL - - - - - {!form.formState.errors.url && ( - - {field.value ? ( - `${NEXT_PUBLIC_WEBAPP_URL()}/org/${field.value}` - ) : ( - A unique URL to identify your organisation - )} - - )} + + - - - )} - /> + + +
+ + )) + .with('create', () => ( + <> + + + Create organisation + - - + + Create an organisation to collaborate with teams + + - - -
- - +
+ +
+ ( + + + Organisation Name + + + + + + + )} + /> + + + {IS_BILLING_ENABLED() ? ( + + ) : ( + + )} + + + +
+
+ + + )) + + .exhaustive()}
); }; + +type BillingPlanFormProps = { + value: string; + onChange: (priceId: string) => void; + plans: InternalClaimPlans; + canCreateFreeOrganisation: boolean; +}; + +const BillingPlanForm = ({ + value, + onChange, + plans, + canCreateFreeOrganisation, +}: BillingPlanFormProps) => { + const { t } = useLingui(); + + const [billingPeriod, setBillingPeriod] = useState<'monthlyPrice' | 'yearlyPrice'>('yearlyPrice'); + + const dynamicPlans = useMemo(() => { + return [INTERNAL_CLAIM_ID.INDIVIDUAL, INTERNAL_CLAIM_ID.PRO, INTERNAL_CLAIM_ID.PLATFORM].map( + (planId) => { + const plan = plans[planId]; + + return { + id: planId, + name: plan.name, + description: parseMessageDescriptorMacro(t, plan.description), + monthlyPrice: plan.monthlyPrice, + yearlyPrice: plan.yearlyPrice, + }; + }, + ); + }, [plans]); + + useEffect(() => { + if (value === '' && !canCreateFreeOrganisation) { + onChange(dynamicPlans[0][billingPeriod]?.id ?? ''); + } + }, [value]); + + const onBillingPeriodChange = (billingPeriod: 'monthlyPrice' | 'yearlyPrice') => { + const plan = dynamicPlans.find((plan) => plan[billingPeriod]?.id === value); + + setBillingPeriod(billingPeriod); + + onChange(plan?.[billingPeriod]?.id ?? Object.keys(plans)[0]); + }; + + return ( +
+ onBillingPeriodChange(value as 'monthlyPrice' | 'yearlyPrice')} + > + + + Monthly + + + Yearly + + + + +
+ + + {dynamicPlans.map((plan) => ( + + ))} + + +
+

+ Enterprise +

+

+ Contact sales here + +

+
+ +
+ +
+ + Compare all plans and features in detail + + +
+
+ ); +}; diff --git a/apps/remix/app/components/dialogs/organisation-delete-dialog.tsx b/apps/remix/app/components/dialogs/organisation-delete-dialog.tsx index 5b23aabf3..d36375620 100644 --- a/apps/remix/app/components/dialogs/organisation-delete-dialog.tsx +++ b/apps/remix/app/components/dialogs/organisation-delete-dialog.tsx @@ -8,6 +8,7 @@ import { useForm } from 'react-hook-form'; import { useNavigate } from 'react-router'; import { z } from 'zod'; +import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; import { AppError } from '@documenso/lib/errors/app-error'; import { trpc } from '@documenso/trpc/react'; import { Button } from '@documenso/ui/primitives/button'; @@ -31,8 +32,6 @@ import { import { Input } from '@documenso/ui/primitives/input'; import { useToast } from '@documenso/ui/primitives/use-toast'; -import { useCurrentOrganisation } from '~/providers/organisation'; - export type OrganisationDeleteDialogProps = { trigger?: React.ReactNode; }; diff --git a/apps/remix/app/components/dialogs/organisation-group-create-dialog.tsx b/apps/remix/app/components/dialogs/organisation-group-create-dialog.tsx index be8aa81e6..d98abff7e 100644 --- a/apps/remix/app/components/dialogs/organisation-group-create-dialog.tsx +++ b/apps/remix/app/components/dialogs/organisation-group-create-dialog.tsx @@ -8,6 +8,7 @@ import type * as DialogPrimitive from '@radix-ui/react-dialog'; import { useForm } from 'react-hook-form'; import type { z } from 'zod'; +import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; import { ORGANISATION_MEMBER_ROLE_HIERARCHY, ORGANISATION_MEMBER_ROLE_MAP, @@ -45,8 +46,6 @@ import { } from '@documenso/ui/primitives/select'; import { useToast } from '@documenso/ui/primitives/use-toast'; -import { useCurrentOrganisation } from '~/providers/organisation'; - export type OrganisationGroupCreateDialogProps = { trigger?: React.ReactNode; } & Omit; diff --git a/apps/remix/app/components/dialogs/organisation-group-delete-dialog.tsx b/apps/remix/app/components/dialogs/organisation-group-delete-dialog.tsx index adeb23f9f..17a389622 100644 --- a/apps/remix/app/components/dialogs/organisation-group-delete-dialog.tsx +++ b/apps/remix/app/components/dialogs/organisation-group-delete-dialog.tsx @@ -4,6 +4,7 @@ import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; +import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; import { trpc } from '@documenso/trpc/react'; import { Alert, AlertDescription } from '@documenso/ui/primitives/alert'; import { Button } from '@documenso/ui/primitives/button'; @@ -18,8 +19,6 @@ import { } from '@documenso/ui/primitives/dialog'; import { useToast } from '@documenso/ui/primitives/use-toast'; -import { useCurrentOrganisation } from '~/providers/organisation'; - export type OrganisationGroupDeleteDialogProps = { organisationGroupId: string; organisationGroupName: string; diff --git a/apps/remix/app/components/dialogs/organisation-leave-dialog.tsx b/apps/remix/app/components/dialogs/organisation-leave-dialog.tsx index ba66c69a4..11aaa01a4 100644 --- a/apps/remix/app/components/dialogs/organisation-leave-dialog.tsx +++ b/apps/remix/app/components/dialogs/organisation-leave-dialog.tsx @@ -1,7 +1,6 @@ import { useState } from 'react'; -import { msg } from '@lingui/core/macro'; -import { useLingui } from '@lingui/react'; +import { useLingui } from '@lingui/react/macro'; import { Trans } from '@lingui/react/macro'; import type { OrganisationMemberRole } from '@prisma/client'; @@ -26,7 +25,6 @@ export type OrganisationLeaveDialogProps = { organisationId: string; organisationName: string; organisationAvatarImageId?: string | null; - organisationMemberId: string; role: OrganisationMemberRole; trigger?: React.ReactNode; }; @@ -36,20 +34,19 @@ export const OrganisationLeaveDialog = ({ organisationId, organisationName, organisationAvatarImageId, - organisationMemberId, role, }: OrganisationLeaveDialogProps) => { const [open, setOpen] = useState(false); - const { _ } = useLingui(); + const { t } = useLingui(); const { toast } = useToast(); const { mutateAsync: leaveOrganisation, isPending: isLeavingOrganisation } = - trpc.organisation.member.delete.useMutation({ + trpc.organisation.leave.useMutation({ onSuccess: () => { toast({ - title: _(msg`Success`), - description: _(msg`You have successfully left this organisation.`), + title: t`Success`, + description: t`You have successfully left this organisation.`, duration: 5000, }); @@ -57,10 +54,8 @@ export const OrganisationLeaveDialog = ({ }, onError: () => { toast({ - title: _(msg`An unknown error occurred`), - description: _( - msg`We encountered an unknown error while attempting to leave this organisation. Please try again later.`, - ), + title: t`An unknown error occurred`, + description: t`We encountered an unknown error while attempting to leave this organisation. Please try again later.`, variant: 'destructive', duration: 10000, }); @@ -94,7 +89,7 @@ export const OrganisationLeaveDialog = ({ avatarSrc={formatAvatarUrl(organisationAvatarImageId)} avatarFallback={organisationName.slice(0, 1).toUpperCase()} primaryText={organisationName} - secondaryText={_(ORGANISATION_MEMBER_ROLE_MAP[role])} + secondaryText={t(ORGANISATION_MEMBER_ROLE_MAP[role])} /> @@ -108,7 +103,7 @@ export const OrganisationLeaveDialog = ({ type="submit" variant="destructive" loading={isLeavingOrganisation} - onClick={async () => leaveOrganisation({ organisationId, organisationMemberId })} + onClick={async () => leaveOrganisation({ organisationId })} > Leave diff --git a/apps/remix/app/components/dialogs/organisation-member-delete-dialog.tsx b/apps/remix/app/components/dialogs/organisation-member-delete-dialog.tsx index d3e36b2f3..a46151d3b 100644 --- a/apps/remix/app/components/dialogs/organisation-member-delete-dialog.tsx +++ b/apps/remix/app/components/dialogs/organisation-member-delete-dialog.tsx @@ -4,6 +4,7 @@ import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; +import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; import { trpc } from '@documenso/trpc/react'; import { Alert } from '@documenso/ui/primitives/alert'; import { AvatarWithText } from '@documenso/ui/primitives/avatar'; @@ -19,8 +20,6 @@ import { } from '@documenso/ui/primitives/dialog'; import { useToast } from '@documenso/ui/primitives/use-toast'; -import { useCurrentOrganisation } from '~/providers/organisation'; - export type OrganisationMemberDeleteDialogProps = { organisationMemberId: string; organisationMemberName: string; diff --git a/apps/remix/app/components/dialogs/organisation-member-invite-dialog.tsx b/apps/remix/app/components/dialogs/organisation-member-invite-dialog.tsx index b4115dcfb..636e455ff 100644 --- a/apps/remix/app/components/dialogs/organisation-member-invite-dialog.tsx +++ b/apps/remix/app/components/dialogs/organisation-member-invite-dialog.tsx @@ -12,6 +12,7 @@ import { useFieldArray, useForm } from 'react-hook-form'; import { z } from 'zod'; import { downloadFile } from '@documenso/lib/client-only/download-file'; +import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; import { ORGANISATION_MEMBER_ROLE_HIERARCHY, ORGANISATION_MEMBER_ROLE_MAP, @@ -49,8 +50,6 @@ import { import { Tabs, TabsContent, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs'; import { useToast } from '@documenso/ui/primitives/use-toast'; -import { useCurrentOrganisation } from '~/providers/organisation'; - export type OrganisationMemberInviteDialogProps = { trigger?: React.ReactNode; } & Omit; @@ -321,7 +320,7 @@ export const OrganisationMemberInviteDialog = ({ {index === 0 && ( - Role + Organisation Role )} diff --git a/apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx b/apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx deleted file mode 100644 index 038c78504..000000000 --- a/apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import { useMemo, useState } from 'react'; - -import { msg } from '@lingui/core/macro'; -import { useLingui } from '@lingui/react'; -import { Trans } from '@lingui/react/macro'; -import type * as DialogPrimitive from '@radix-ui/react-dialog'; -import { AnimatePresence, motion } from 'framer-motion'; -import { Loader, TagIcon } from 'lucide-react'; - -import { trpc } from '@documenso/trpc/react'; -import { Button } from '@documenso/ui/primitives/button'; -import { Card, CardContent } from '@documenso/ui/primitives/card'; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@documenso/ui/primitives/dialog'; -import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs'; -import { useToast } from '@documenso/ui/primitives/use-toast'; - -export type TeamCheckoutCreateDialogProps = { - pendingTeamId: number | null; - onClose: () => void; -} & Omit; - -const MotionCard = motion(Card); - -export const TeamCheckoutCreateDialog = ({ - pendingTeamId, - onClose, - ...props -}: TeamCheckoutCreateDialogProps) => { - const { _ } = useLingui(); - const { toast } = useToast(); - - const [interval, setInterval] = useState<'monthly' | 'yearly'>('monthly'); - - const { data, isLoading } = trpc.team.getTeamPrices.useQuery(); - - const { mutateAsync: createCheckout, isPending: isCreatingCheckout } = - trpc.team.createTeamPendingCheckout.useMutation({ - onSuccess: (checkoutUrl) => { - window.open(checkoutUrl, '_blank'); - onClose(); - }, - onError: () => - toast({ - title: _(msg`Something went wrong`), - description: _( - msg`We were unable to create a checkout session. Please try again, or contact support`, - ), - variant: 'destructive', - }), - }); - - const selectedPrice = useMemo(() => { - if (!data) { - return null; - } - - return data[interval]; - }, [data, interval]); - - const handleOnOpenChange = (open: boolean) => { - if (pendingTeamId === null) { - return; - } - - if (!open) { - onClose(); - } - }; - - if (pendingTeamId === null) { - return null; - } - - return ( - - - - - Team checkout - - - - Payment is required to finalise the creation of your team. - - - - {(isLoading || !data) && ( -
- {isLoading ? ( - - ) : ( -

- Something went wrong -

- )} -
- )} - - {data && selectedPrice && !isLoading && ( -
- setInterval(value as 'monthly' | 'yearly')} - value={interval} - className="mb-4" - > - - {[data.monthly, data.yearly].map((price) => ( - - {price.friendlyInterval} - - ))} - - - - - - - {selectedPrice.interval === 'monthly' ? ( -
- $50 USD per month -
- ) : ( -
- - $480 USD per year - -
- - 20% off -
-
- )} - -
-

- This price includes minimum 5 seats. -

- -

- Adding and removing seats will adjust your invoice accordingly. -

-
-
-
-
- - - - - - -
- )} -
-
- ); -}; diff --git a/apps/remix/app/components/dialogs/team-create-dialog.tsx b/apps/remix/app/components/dialogs/team-create-dialog.tsx index e07117e4d..2d7c016e9 100644 --- a/apps/remix/app/components/dialogs/team-create-dialog.tsx +++ b/apps/remix/app/components/dialogs/team-create-dialog.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { zodResolver } from '@hookform/resolvers/zod'; import { msg } from '@lingui/core/macro'; @@ -7,14 +7,16 @@ import { Trans } from '@lingui/react/macro'; import type * as DialogPrimitive from '@radix-ui/react-dialog'; import { useForm } from 'react-hook-form'; import { useSearchParams } from 'react-router'; -import { useNavigate } from 'react-router'; import type { z } from 'zod'; import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params'; +import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; +import { useSession } from '@documenso/lib/client-only/providers/session'; import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import { trpc } from '@documenso/trpc/react'; import { ZCreateTeamRequestSchema } from '@documenso/trpc/server/team-router/create-team.types'; +import { Alert, AlertDescription } from '@documenso/ui/primitives/alert'; import { Button } from '@documenso/ui/primitives/button'; import { Checkbox } from '@documenso/ui/primitives/checkbox'; import { @@ -35,10 +37,9 @@ import { FormMessage, } from '@documenso/ui/primitives/form/form'; import { Input } from '@documenso/ui/primitives/input'; +import { SpinnerBox } from '@documenso/ui/primitives/spinner'; import { useToast } from '@documenso/ui/primitives/use-toast'; -import { useCurrentOrganisation } from '~/providers/organisation'; - export type TeamCreateDialogProps = { trigger?: React.ReactNode; onCreated?: () => Promise; @@ -55,14 +56,18 @@ type TCreateTeamFormSchema = z.infer; export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDialogProps) => { const { _ } = useLingui(); const { toast } = useToast(); + const { refreshSession } = useSession(); - const navigate = useNavigate(); const [searchParams] = useSearchParams(); const updateSearchParams = useUpdateSearchParams(); const organisation = useCurrentOrganisation(); const [open, setOpen] = useState(false); + const { data: fullOrganisation } = trpc.organisation.get.useQuery({ + organisationReference: organisation.id, + }); + const actionSearchParam = searchParams?.get('action'); const form = useForm({ @@ -78,7 +83,7 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia const onFormSubmit = async ({ teamName, teamUrl, inheritMembers }: TCreateTeamFormSchema) => { try { - const response = await createTeam({ + await createTeam({ organisationId: organisation.id, teamName, teamUrl, @@ -87,12 +92,8 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia setOpen(false); - if (response.paymentRequired) { - await navigate(`/settings/teams?tab=pending&checkout=${response.pendingTeamId}`); - return; - } - await onCreated?.(); + await refreshSession(); toast({ title: _(msg`Success`), @@ -125,6 +126,22 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia return text.toLowerCase().replace(/\s+/g, '-'); }; + const dialogState = useMemo(() => { + if (!fullOrganisation) { + return 'loading'; + } + + if (fullOrganisation.organisationClaim.teamCount === 0) { + return 'form'; + } + + if (fullOrganisation.organisationClaim.teamCount <= fullOrganisation.teams.length) { + return 'alert'; + } + + return 'form'; + }, [fullOrganisation]); + useEffect(() => { if (actionSearchParam === 'add-team') { setOpen(true); @@ -161,109 +178,136 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia -
- -
} + + {dialogState === 'alert' && ( + <> + - ( - - - Team Name - - - { - const oldGeneratedUrl = mapTextToUrl(field.value); - const newGeneratedUrl = mapTextToUrl(event.target.value); + + + You have reached the maximum number of teams for your plan. Please contact sales + at support@documenso.com if you would + like to adjust your plan. + + + - const urlField = form.getValues('teamUrl'); - if (urlField === oldGeneratedUrl) { - form.setValue('teamUrl', newGeneratedUrl); - } + + + + + )} - field.onChange(event); - }} - /> - - - - )} - /> + {dialogState === 'form' && ( + + +
+ ( + + + Team Name + + + { + const oldGeneratedUrl = mapTextToUrl(field.value); + const newGeneratedUrl = mapTextToUrl(event.target.value); - ( - - - Team URL - - - - - {!form.formState.errors.teamUrl && ( - - {field.value ? ( - `${NEXT_PUBLIC_WEBAPP_URL()}/t/${field.value}` - ) : ( - A unique URL to identify your team - )} - - )} + const urlField = form.getValues('teamUrl'); + if (urlField === oldGeneratedUrl) { + form.setValue('teamUrl', newGeneratedUrl); + } - - - )} - /> - - ( - - -
- + + + + )} + /> - -
-
-
- )} - /> + ( + + + Team URL + + + + + {!form.formState.errors.teamUrl && ( + + {field.value ? ( + `${NEXT_PUBLIC_WEBAPP_URL()}/t/${field.value}` + ) : ( + A unique URL to identify your team + )} + + )} - - + + + )} + /> - - -
- - + ( + + +
+ + + +
+
+
+ )} + /> + + + + + + +
+ + + )} ); diff --git a/apps/remix/app/components/dialogs/team-delete-dialog.tsx b/apps/remix/app/components/dialogs/team-delete-dialog.tsx index 699802234..670ba1a2a 100644 --- a/apps/remix/app/components/dialogs/team-delete-dialog.tsx +++ b/apps/remix/app/components/dialogs/team-delete-dialog.tsx @@ -8,6 +8,7 @@ import { useForm } from 'react-hook-form'; import { useNavigate } from 'react-router'; import { z } from 'zod'; +import { useSession } from '@documenso/lib/client-only/providers/session'; import { AppError } from '@documenso/lib/errors/app-error'; import { trpc } from '@documenso/trpc/react'; import { Button } from '@documenso/ui/primitives/button'; @@ -50,6 +51,7 @@ export const TeamDeleteDialog = ({ const { _ } = useLingui(); const { toast } = useToast(); + const { refreshSession } = useSession(); const deleteMessage = _(msg`delete ${teamName}`); @@ -72,6 +74,8 @@ export const TeamDeleteDialog = ({ try { await deleteTeam({ teamId }); + await refreshSession(); + toast({ title: _(msg`Success`), description: _(msg`Your team has been successfully deleted.`), diff --git a/apps/remix/app/components/dialogs/team-email-add-dialog.tsx b/apps/remix/app/components/dialogs/team-email-add-dialog.tsx index 161c2c0eb..56e54da72 100644 --- a/apps/remix/app/components/dialogs/team-email-add-dialog.tsx +++ b/apps/remix/app/components/dialogs/team-email-add-dialog.tsx @@ -61,12 +61,12 @@ export const TeamEmailAddDialog = ({ teamId, trigger, ...props }: TeamEmailAddDi }, }); - const { mutateAsync: createTeamEmailVerification, isPending } = - trpc.team.createTeamEmailVerification.useMutation(); + const { mutateAsync: sendTeamEmailVerification, isPending } = + trpc.team.email.verification.send.useMutation(); const onFormSubmit = async ({ name, email }: TCreateTeamEmailFormSchema) => { try { - await createTeamEmailVerification({ + await sendTeamEmailVerification({ teamId, name, email, diff --git a/apps/remix/app/components/dialogs/team-email-delete-dialog.tsx b/apps/remix/app/components/dialogs/team-email-delete-dialog.tsx index ec050961c..d9b780657 100644 --- a/apps/remix/app/components/dialogs/team-email-delete-dialog.tsx +++ b/apps/remix/app/components/dialogs/team-email-delete-dialog.tsx @@ -48,7 +48,7 @@ export const TeamEmailDeleteDialog = ({ trigger, teamName, team }: TeamEmailDele const { revalidate } = useRevalidator(); const { mutateAsync: deleteTeamEmail, isPending: isDeletingTeamEmail } = - trpc.team.deleteTeamEmail.useMutation({ + trpc.team.email.delete.useMutation({ onSuccess: () => { toast({ title: _(msg`Success`), @@ -67,7 +67,7 @@ export const TeamEmailDeleteDialog = ({ trigger, teamName, team }: TeamEmailDele }); const { mutateAsync: deleteTeamEmailVerification, isPending: isDeletingTeamEmailVerification } = - trpc.team.deleteTeamEmailVerification.useMutation({ + trpc.team.email.verification.delete.useMutation({ onSuccess: () => { toast({ title: _(msg`Success`), diff --git a/apps/remix/app/components/dialogs/team-email-update-dialog.tsx b/apps/remix/app/components/dialogs/team-email-update-dialog.tsx index bde700949..dd5411b59 100644 --- a/apps/remix/app/components/dialogs/team-email-update-dialog.tsx +++ b/apps/remix/app/components/dialogs/team-email-update-dialog.tsx @@ -61,7 +61,7 @@ export const TeamEmailUpdateDialog = ({ }, }); - const { mutateAsync: updateTeamEmail } = trpc.team.updateTeamEmail.useMutation(); + const { mutateAsync: updateTeamEmail } = trpc.team.email.update.useMutation(); const onFormSubmit = async ({ name }: TUpdateTeamEmailFormSchema) => { try { diff --git a/apps/remix/app/components/forms/branding-preferences-form.tsx b/apps/remix/app/components/forms/branding-preferences-form.tsx index 61ffa1e5d..6deb75d14 100644 --- a/apps/remix/app/components/forms/branding-preferences-form.tsx +++ b/apps/remix/app/components/forms/branding-preferences-form.tsx @@ -98,13 +98,6 @@ export function BrandingPreferencesForm({ settings, onFormSubmit }: BrandingPref }; }, [previewUrl]); - // Todo: orgs remove - useEffect(() => { - console.log({ - errors: form.formState.errors, - }); - }, [form.formState.errors]); - return (
diff --git a/apps/remix/app/components/forms/organisation-update-form.tsx b/apps/remix/app/components/forms/organisation-update-form.tsx index 27ea6a73d..06e1c0dc3 100644 --- a/apps/remix/app/components/forms/organisation-update-form.tsx +++ b/apps/remix/app/components/forms/organisation-update-form.tsx @@ -7,6 +7,7 @@ import { useForm } from 'react-hook-form'; import { useNavigate } from 'react-router'; import type { z } from 'zod'; +import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import { trpc } from '@documenso/trpc/react'; @@ -23,8 +24,6 @@ import { import { Input } from '@documenso/ui/primitives/input'; import { useToast } from '@documenso/ui/primitives/use-toast'; -import { useCurrentOrganisation } from '~/providers/organisation'; - const ZOrganisationUpdateFormSchema = ZUpdateOrganisationRequestSchema.shape.data.pick({ name: true, url: true, diff --git a/apps/remix/app/components/forms/signin.tsx b/apps/remix/app/components/forms/signin.tsx index 79edbf849..b3ca9cdb6 100644 --- a/apps/remix/app/components/forms/signin.tsx +++ b/apps/remix/app/components/forms/signin.tsx @@ -55,7 +55,7 @@ const handleFallbackErrorMessages = (code: string) => { return message; }; -const LOGIN_REDIRECT_PATH = '/documents'; +const LOGIN_REDIRECT_PATH = '/dashboard'; export const ZSignInFormSchema = z.object({ email: z.string().email().min(1), diff --git a/apps/remix/app/components/forms/signup.tsx b/apps/remix/app/components/forms/signup.tsx index b26f56742..4ee6517d5 100644 --- a/apps/remix/app/components/forms/signup.tsx +++ b/apps/remix/app/components/forms/signup.tsx @@ -1,11 +1,10 @@ -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; import { zodResolver } from '@hookform/resolvers/zod'; import type { MessageDescriptor } from '@lingui/core'; import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; -import { AnimatePresence, motion } from 'framer-motion'; import { useForm } from 'react-hook-form'; import { FaIdCardClip } from 'react-icons/fa6'; import { FcGoogle } from 'react-icons/fc'; @@ -15,7 +14,6 @@ import { z } from 'zod'; import communityCardsImage from '@documenso/assets/images/community-cards.png'; import { authClient } from '@documenso/auth/client'; import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics'; -import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import { ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema'; import { cn } from '@documenso/ui/lib/utils'; @@ -33,11 +31,8 @@ import { PasswordInput } from '@documenso/ui/primitives/password-input'; import { SignaturePadDialog } from '@documenso/ui/primitives/signature-pad/signature-pad-dialog'; import { useToast } from '@documenso/ui/primitives/use-toast'; -import { UserProfileSkeleton } from '~/components/general/user-profile-skeleton'; import { UserProfileTimur } from '~/components/general/user-profile-timur'; -type SignUpStep = 'BASIC_DETAILS' | 'CLAIM_USERNAME'; - export const ZSignUpFormSchema = z .object({ name: z @@ -47,14 +42,6 @@ export const ZSignUpFormSchema = z email: z.string().email().min(1), password: ZPasswordSchema, signature: z.string().min(1, { message: msg`We need your signature to sign documents`.id }), - url: z - .string() - .trim() - .toLowerCase() - .min(1, { message: msg`We need a username to create your profile`.id }) - .regex(/^[a-z0-9-]+$/, { - message: msg`Username can only container alphanumeric characters and dashes.`.id, - }), }) .refine( (data) => { @@ -71,8 +58,6 @@ export const signupErrorMessages: Record = { SIGNUP_DISABLED: msg`Signups are disabled.`, [AppErrorCode.ALREADY_EXISTS]: msg`User with this email already exists. Please use a different email address.`, [AppErrorCode.INVALID_REQUEST]: msg`We were unable to create your account. Please review the information you provided and try again.`, - PROFILE_URL_TAKEN: msg`This username has already been taken`, - PREMIUM_PROFILE_URL: msg`Only subscribers can have a username shorter than 6 characters`, }; export type TSignUpFormSchema = z.infer; @@ -97,19 +82,14 @@ export const SignUpForm = ({ const navigate = useNavigate(); const [searchParams] = useSearchParams(); - const [step, setStep] = useState('BASIC_DETAILS'); - const utmSrc = searchParams.get('utm_source') ?? null; - const baseUrl = new URL(NEXT_PUBLIC_WEBAPP_URL() ?? 'http://localhost:3000'); - const form = useForm({ values: { name: '', email: initialEmail ?? '', password: '', signature: '', - url: '', }, mode: 'onBlur', resolver: zodResolver(ZSignUpFormSchema), @@ -117,17 +97,13 @@ export const SignUpForm = ({ const isSubmitting = form.formState.isSubmitting; - const name = form.watch('name'); - const url = form.watch('url'); - - const onFormSubmit = async ({ name, email, password, signature, url }: TSignUpFormSchema) => { + const onFormSubmit = async ({ name, email, password, signature }: TSignUpFormSchema) => { try { await authClient.emailPassword.signUp({ name, email, password, signature, - url, }); await navigate(`/unverified-account`); @@ -150,26 +126,11 @@ export const SignUpForm = ({ const errorMessage = signupErrorMessages[error.code] ?? signupErrorMessages.INVALID_REQUEST; - if (error.code === 'PROFILE_URL_TAKEN' || error.code === 'PREMIUM_PROFILE_URL') { - form.setError('url', { - type: 'manual', - message: _(errorMessage), - }); - } else { - toast({ - title: _(msg`An error occurred`), - description: _(errorMessage), - variant: 'destructive', - }); - } - } - }; - - const onNextClick = async () => { - const valid = await form.trigger(['name', 'email', 'password', 'signature']); - - if (valid) { - setStep('CLAIM_USERNAME'); + toast({ + title: _(msg`An error occurred`), + description: _(errorMessage), + variant: 'destructive', + }); } }; @@ -231,59 +192,30 @@ export const SignUpForm = ({ User profiles are here! - - {step === 'BASIC_DETAILS' ? ( - - - - ) : ( - - - - )} - +
+ +
- {step === 'BASIC_DETAILS' && ( -
-

- Create a new account -

+
+

+ Create a new account +

-

- - Create your account and start using state-of-the-art document signing. Open and - beautiful signing is within your grasp. - -

-
- )} - - {step === 'CLAIM_USERNAME' && ( -
-

- Claim your username now -

- -

- - You will get notified & be able to set up your documenso public profile when we - launch the feature. - -

-
- )} +

+ + Create your account and start using state-of-the-art document signing. Open and + beautiful signing is within your grasp. + +

+

@@ -292,242 +224,147 @@ export const SignUpForm = ({ className="flex w-full flex-1 flex-col gap-y-4" onSubmit={form.handleSubmit(onFormSubmit)} > - {step === 'BASIC_DETAILS' && ( -
+ ( + + + Full Name + + + + + + )} - disabled={isSubmitting} - > - ( - - - Full Name - - - - - - - )} - /> + /> - ( - - - Email Address - - - - - - - )} - /> - - ( - - - Password - - - - - - - - - )} - /> - - ( - - - Sign Here - - - onChange(v ?? '')} - /> - - - - - )} - /> - - {(isGoogleSSOEnabled || isOIDCSSOEnabled) && ( - <> -
-
- - Or - -
-
- + ( + + + Email Address + + + + + + )} + /> - {isGoogleSSOEnabled && ( - <> - - + ( + + + Password + + + + + + + + )} + /> - {isOIDCSSOEnabled && ( - <> - - + ( + + + Sign Here + + + onChange(v ?? '')} + /> + + + + )} + /> -

- - Already have an account?{' '} - - Sign in instead - - -

-
- )} - - {step === 'CLAIM_USERNAME' && ( -
- ( - - - Public profile username - - - - - - - - -
- {baseUrl.host}/u/{field.value || ''} -
-
- )} - /> -
- )} - -
- {step === 'BASIC_DETAILS' && ( -

- - Basic details - {' '} - 1/2 -

+ {(isGoogleSSOEnabled || isOIDCSSOEnabled) && ( + <> +
+
+ + Or + +
+
+ )} - {step === 'CLAIM_USERNAME' && ( -

- - Claim username - {' '} - 2/2 -

+ {isGoogleSSOEnabled && ( + <> + + )} -
- -
-
- -
- {/* Go back button, disabled if step is basic details */} - - - {/* Continue button */} - {step === 'BASIC_DETAILS' && ( - + {isOIDCSSOEnabled && ( + <> + + )} - {/* Sign up button */} - {step === 'CLAIM_USERNAME' && ( - - )} -
+

+ + Already have an account?{' '} + + Sign in instead + + +

+ + +

diff --git a/apps/remix/app/components/forms/subscription-claim-form.tsx b/apps/remix/app/components/forms/subscription-claim-form.tsx new file mode 100644 index 000000000..0a26b2ab6 --- /dev/null +++ b/apps/remix/app/components/forms/subscription-claim-form.tsx @@ -0,0 +1,155 @@ +import { zodResolver } from '@hookform/resolvers/zod'; +import { Trans, useLingui } from '@lingui/react/macro'; +import type { SubscriptionClaim } from '@prisma/client'; +import { useForm } from 'react-hook-form'; +import type { z } from 'zod'; + +import { SUBSCRIPTION_CLAIM_FEATURE_FLAGS } from '@documenso/lib/types/subscription'; +import { ZCreateSubscriptionClaimRequestSchema } from '@documenso/trpc/server/admin-router/create-subscription-claim.types'; +import { Checkbox } from '@documenso/ui/primitives/checkbox'; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@documenso/ui/primitives/form/form'; +import { Input } from '@documenso/ui/primitives/input'; + +export type SubscriptionClaimFormValues = z.infer; + +type SubscriptionClaimFormProps = { + subscriptionClaim: Omit; + onFormSubmit: (data: SubscriptionClaimFormValues) => Promise; + formSubmitTrigger?: React.ReactNode; +}; + +export const SubscriptionClaimForm = ({ + subscriptionClaim, + onFormSubmit, + formSubmitTrigger, +}: SubscriptionClaimFormProps) => { + const { t } = useLingui(); + + const form = useForm({ + resolver: zodResolver(ZCreateSubscriptionClaimRequestSchema), + defaultValues: { + name: subscriptionClaim.name, + teamCount: subscriptionClaim.teamCount, + memberCount: subscriptionClaim.memberCount, + flags: subscriptionClaim.flags, + }, + }); + + return ( +

+ +
+ ( + + + Name + + + + + + + )} + /> + + ( + + + Team Count + + + field.onChange(parseInt(e.target.value, 10) || 0)} + /> + + + Number of teams allowed. 0 = Unlimited + + + + )} + /> + + ( + + + Member Count + + + field.onChange(parseInt(e.target.value, 10) || 0)} + /> + + + Number of members allowed. 0 = Unlimited + + + + )} + /> + +
+ + Feature Flags + + +
+ {Object.values(SUBSCRIPTION_CLAIM_FEATURE_FLAGS).map(({ key, label }) => ( + ( + + +
+ + + +
+
+
+ )} + /> + ))} +
+
+ + {formSubmitTrigger} +
+
+ + ); +}; diff --git a/apps/remix/app/components/general/billing-plans.tsx b/apps/remix/app/components/general/billing-plans.tsx index 2113da8fc..654fa276b 100644 --- a/apps/remix/app/components/general/billing-plans.tsx +++ b/apps/remix/app/components/general/billing-plans.tsx @@ -1,62 +1,48 @@ -import { useState } from 'react'; +import { useMemo, useState } from 'react'; -import type { MessageDescriptor } from '@lingui/core'; import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; import { AnimatePresence, motion } from 'framer-motion'; -import type { PriceIntervals } from '@documenso/ee/server-only/stripe/get-prices-by-interval'; +import type { InternalClaimPlans } from '@documenso/ee/server-only/stripe/get-internal-claim-plans'; import { useIsMounted } from '@documenso/lib/client-only/hooks/use-is-mounted'; -import { toHumanPrice } from '@documenso/lib/universal/stripe/to-human-price'; +import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; import { trpc } from '@documenso/trpc/react'; import { Button } from '@documenso/ui/primitives/button'; import { Card, CardContent, CardTitle } from '@documenso/ui/primitives/card'; import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs'; import { useToast } from '@documenso/ui/primitives/use-toast'; -type Interval = keyof PriceIntervals; - -const INTERVALS: Interval[] = ['day', 'week', 'month', 'year']; - -// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -const isInterval = (value: unknown): value is Interval => INTERVALS.includes(value as Interval); - -const FRIENDLY_INTERVALS: Record = { - day: msg`Daily`, - week: msg`Weekly`, - month: msg`Monthly`, - year: msg`Yearly`, -}; - const MotionCard = motion(Card); export type BillingPlansProps = { - prices: PriceIntervals; + plans: InternalClaimPlans; }; -export const BillingPlans = ({ prices }: BillingPlansProps) => { +export const BillingPlans = ({ plans }: BillingPlansProps) => { const { _ } = useLingui(); const { toast } = useToast(); const isMounted = useIsMounted(); - const [interval, setInterval] = useState('month'); + const organisation = useCurrentOrganisation(); + + const [interval, setInterval] = useState<'monthlyPrice' | 'yearlyPrice'>('yearlyPrice'); const [checkoutSessionPriceId, setCheckoutSessionPriceId] = useState(null); - const { mutateAsync: createCheckoutSession } = trpc.profile.createCheckoutSession.useMutation(); + const { mutateAsync: createSubscription } = trpc.billing.subscription.create.useMutation(); const onSubscribeClick = async (priceId: string) => { try { setCheckoutSessionPriceId(priceId); - const url = await createCheckoutSession({ priceId }); + const { redirectUrl } = await createSubscription({ + organisationId: organisation.id, + priceId, + }); - if (!url) { - throw new Error('Unable to create session'); - } - - window.open(url); + window.open(redirectUrl, '_blank'); } catch (_err) { toast({ title: _(msg`Something went wrong`), @@ -68,24 +54,37 @@ export const BillingPlans = ({ prices }: BillingPlansProps) => { } }; + const pricesToDisplay = useMemo(() => { + const prices = []; + + for (const plan of Object.values(plans)) { + if (plan[interval] && plan[interval].isVisibleInApp) { + prices.push(plan[interval]); + } + } + + return prices; + }, [plans, interval]); + return (
- isInterval(value) && setInterval(value)}> + setInterval(value as 'monthlyPrice' | 'yearlyPrice')} + > - {INTERVALS.map( - (interval) => - prices[interval].length > 0 && ( - - {_(FRIENDLY_INTERVALS[interval])} - - ), - )} + + Monthly + + + Yearly +
- {prices[interval].map((price) => ( + {pricesToDisplay.map((price) => ( { {price.product.name}
- ${toHumanPrice(price.unit_amount ?? 0)} {price.currency.toUpperCase()}{' '} - per {interval} + {price.friendlyPrice + ' '} + + {interval === 'monthlyPrice' ? ( + per month + ) : ( + per year + )} +
diff --git a/apps/remix/app/components/general/billing-portal-button.tsx b/apps/remix/app/components/general/billing-portal-button.tsx deleted file mode 100644 index ea8735954..000000000 --- a/apps/remix/app/components/general/billing-portal-button.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { msg } from '@lingui/core/macro'; -import { useLingui } from '@lingui/react'; -import { Trans } from '@lingui/react/macro'; - -import { trpc } from '@documenso/trpc/react'; -import { Button } from '@documenso/ui/primitives/button'; -import { useToast } from '@documenso/ui/primitives/use-toast'; - -export type BillingPortalButtonProps = { - buttonProps?: React.ComponentProps; - children?: React.ReactNode; -}; - -export const BillingPortalButton = ({ buttonProps, children }: BillingPortalButtonProps) => { - const { _ } = useLingui(); - const { toast } = useToast(); - - const { mutateAsync: createBillingPortal, isPending } = - trpc.profile.createBillingPortal.useMutation({ - onSuccess: (sessionUrl) => { - window.open(sessionUrl, '_blank'); - }, - onError: (err) => { - let description = _( - msg`We are unable to proceed to the billing portal at this time. Please try again, or contact support.`, - ); - - if (err.message === 'CUSTOMER_NOT_FOUND') { - description = _( - msg`You do not currently have a customer record, this should not happen. Please contact support for assistance.`, - ); - } - - toast({ - title: _(msg`Something went wrong`), - description, - variant: 'destructive', - duration: 10000, - }); - }, - }); - - return ( - - ); -}; diff --git a/apps/remix/app/components/general/document/document-edit-form.tsx b/apps/remix/app/components/general/document/document-edit-form.tsx index 5ede9d1f3..98132986b 100644 --- a/apps/remix/app/components/general/document/document-edit-form.tsx +++ b/apps/remix/app/components/general/document/document-edit-form.tsx @@ -35,7 +35,6 @@ export type DocumentEditFormProps = { className?: string; initialDocument: TDocument; documentRootPath: string; - isDocumentEnterprise: boolean; }; type EditDocumentStep = 'settings' | 'signers' | 'fields' | 'subject'; @@ -45,7 +44,6 @@ export const DocumentEditForm = ({ className, initialDocument, documentRootPath, - isDocumentEnterprise, }: DocumentEditFormProps) => { const { toast } = useToast(); const { _ } = useLingui(); @@ -358,7 +356,6 @@ export const DocumentEditForm = ({ currentTeamMemberRole={team.currentTeamRole} recipients={recipients} fields={fields} - isDocumentEnterprise={isDocumentEnterprise} isDocumentPdfLoaded={isDocumentPdfLoaded} onSubmit={onAddSettingsFormSubmit} /> @@ -370,7 +367,6 @@ export const DocumentEditForm = ({ signingOrder={document.documentMeta?.signingOrder} allowDictateNextSigner={document.documentMeta?.allowDictateNextSigner} fields={fields} - isDocumentEnterprise={isDocumentEnterprise} onSubmit={onAddSignersFormSubmit} isDocumentPdfLoaded={isDocumentPdfLoaded} /> diff --git a/apps/remix/app/components/general/menu-switcher.tsx b/apps/remix/app/components/general/menu-switcher.tsx index 9e120873c..a802e53d6 100644 --- a/apps/remix/app/components/general/menu-switcher.tsx +++ b/apps/remix/app/components/general/menu-switcher.tsx @@ -14,6 +14,7 @@ import { import { Link, useLocation } from 'react-router'; import { authClient } from '@documenso/auth/client'; +import { useOptionalCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; import { useSession } from '@documenso/lib/client-only/providers/session'; import { EXTENDED_ORGANISATION_MEMBER_ROLE_MAP } from '@documenso/lib/constants/organisations'; import { EXTENDED_TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams'; @@ -34,7 +35,6 @@ import { DropdownMenuTrigger, } from '@documenso/ui/primitives/dropdown-menu'; -import { useOptionalCurrentOrganisation } from '~/providers/organisation'; import { useOptionalCurrentTeam } from '~/providers/team'; export const MenuSwitcher = () => { @@ -152,10 +152,7 @@ export const MenuSwitcher = () => { @@ -211,9 +208,9 @@ export const MenuSwitcher = () => { ))}
diff --git a/apps/remix/app/components/general/teams/team-layout-billing-banner.tsx b/apps/remix/app/components/general/organisations/organisation-billing-banner.tsx similarity index 84% rename from apps/remix/app/components/general/teams/team-layout-billing-banner.tsx rename to apps/remix/app/components/general/organisations/organisation-billing-banner.tsx index 68f013fac..ead60c070 100644 --- a/apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +++ b/apps/remix/app/components/general/organisations/organisation-billing-banner.tsx @@ -3,12 +3,12 @@ import { useState } from 'react'; import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; -import type { TeamMemberRole } from '@prisma/client'; import { SubscriptionStatus } from '@prisma/client'; import { AlertTriangle } from 'lucide-react'; import { match } from 'ts-pattern'; -import { canExecuteTeamAction } from '@documenso/lib/utils/teams'; +import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; +import { canExecuteOrganisationAction } from '@documenso/lib/utils/organisations'; import { trpc } from '@documenso/trpc/react'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; @@ -21,30 +21,28 @@ import { } from '@documenso/ui/primitives/dialog'; import { useToast } from '@documenso/ui/primitives/use-toast'; -export type TeamLayoutBillingBannerProps = { +export type OrganisationBillingBannerProps = { subscriptionStatus: SubscriptionStatus; - teamId: number; - userRole: TeamMemberRole; }; -export const TeamLayoutBillingBanner = ({ +export const OrganisationBillingBanner = ({ subscriptionStatus, - teamId, - userRole, -}: TeamLayoutBillingBannerProps) => { +}: OrganisationBillingBannerProps) => { const { _ } = useLingui(); const { toast } = useToast(); const [isOpen, setIsOpen] = useState(false); - const { mutateAsync: createBillingPortal, isPending } = - trpc.team.createBillingPortal.useMutation(); + const organisation = useCurrentOrganisation(); + + const { mutateAsync: manageSubscription, isPending } = + trpc.billing.subscription.manage.useMutation(); const handleCreatePortal = async () => { try { - const sessionUrl = await createBillingPortal({ teamId }); + const { redirectUrl } = await manageSubscription({ organisationId: organisation.id }); - window.open(sessionUrl, '_blank'); + window.open(redirectUrl, '_blank'); setIsOpen(false); } catch (err) { @@ -125,7 +123,7 @@ export const TeamLayoutBillingBanner = ({ )) .otherwise(() => null)} - {canExecuteTeamAction('MANAGE_BILLING', userRole) && ( + {canExecuteOrganisationAction('MANAGE_BILLING', organisation.currentOrganisationRole) && ( + ); +}; diff --git a/apps/remix/app/components/general/organisations/organisation-invitations.tsx b/apps/remix/app/components/general/organisations/organisation-invitations.tsx index 59daf777d..3ad3a7fca 100644 --- a/apps/remix/app/components/general/organisations/organisation-invitations.tsx +++ b/apps/remix/app/components/general/organisations/organisation-invitations.tsx @@ -5,6 +5,7 @@ import { OrganisationMemberInviteStatus } from '@prisma/client'; import { AnimatePresence } from 'framer-motion'; import { BellIcon } from 'lucide-react'; +import { useSession } from '@documenso/lib/client-only/providers/session'; import { formatAvatarUrl } from '@documenso/lib/utils/avatars'; import { trpc } from '@documenso/trpc/react'; import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out'; @@ -21,7 +22,7 @@ import { } from '@documenso/ui/primitives/dialog'; import { useToast } from '@documenso/ui/primitives/use-toast'; -export const OrganisationInvitations = () => { +export const OrganisationInvitations = ({ className }: { className?: string }) => { const { data, isLoading } = trpc.organisation.member.invite.getMany.useQuery({ status: OrganisationMemberInviteStatus.PENDING, }); @@ -30,7 +31,7 @@ export const OrganisationInvitations = () => { {data && data.length > 0 && !isLoading && ( - +
@@ -83,23 +84,25 @@ export const OrganisationInvitations = () => {
    {data.map((invitation) => (
  • - - {invitation.organisation.name} - - } - // secondaryText={formatOrganisationUrl(invitation.team.url)} - rightSideComponent={ -
    - - -
    - } - /> + + + {invitation.organisation.name} + + } + secondaryText={`/orgs/${invitation.organisation.url}`} + rightSideComponent={ +
    + + +
    + } + /> +
  • ))}
@@ -116,13 +119,16 @@ export const OrganisationInvitations = () => { const AcceptOrganisationInvitationButton = ({ token }: { token: string }) => { const { _ } = useLingui(); const { toast } = useToast(); + const { refreshSession } = useSession(); const { mutateAsync: acceptOrganisationInvitation, isPending, isSuccess, } = trpc.organisation.member.invite.accept.useMutation({ - onSuccess: () => { + onSuccess: async () => { + await refreshSession(); + toast({ title: _(msg`Success`), description: _(msg`Invitation accepted`), @@ -153,13 +159,16 @@ const AcceptOrganisationInvitationButton = ({ token }: { token: string }) => { const DeclineOrganisationInvitationButton = ({ token }: { token: string }) => { const { _ } = useLingui(); const { toast } = useToast(); + const { refreshSession } = useSession(); const { mutateAsync: declineOrganisationInvitation, isPending, isSuccess, } = trpc.organisation.member.invite.decline.useMutation({ - onSuccess: () => { + onSuccess: async () => { + await refreshSession(); + toast({ title: _(msg`Success`), description: _(msg`Invitation declined`), diff --git a/apps/remix/app/components/general/settings-nav-desktop.tsx b/apps/remix/app/components/general/settings-nav-desktop.tsx index 1a6d7b8ee..af29c5cfe 100644 --- a/apps/remix/app/components/general/settings-nav-desktop.tsx +++ b/apps/remix/app/components/general/settings-nav-desktop.tsx @@ -1,11 +1,10 @@ import type { HTMLAttributes } from 'react'; import { Trans } from '@lingui/react/macro'; -import { CreditCard, Lock, User, Users } from 'lucide-react'; +import { Lock, User, Users } from 'lucide-react'; import { useLocation } from 'react-router'; import { Link } from 'react-router'; -import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; @@ -14,8 +13,6 @@ export type SettingsDesktopNavProps = HTMLAttributes; export const SettingsDesktopNav = ({ className, ...props }: SettingsDesktopNavProps) => { const { pathname } = useLocation(); - const isBillingEnabled = IS_BILLING_ENABLED(); - return (
@@ -56,21 +53,6 @@ export const SettingsDesktopNav = ({ className, ...props }: SettingsDesktopNavPr Security - - {isBillingEnabled && ( - - - - )}
); }; diff --git a/apps/remix/app/components/general/settings-nav-mobile.tsx b/apps/remix/app/components/general/settings-nav-mobile.tsx index 759c52639..13be53464 100644 --- a/apps/remix/app/components/general/settings-nav-mobile.tsx +++ b/apps/remix/app/components/general/settings-nav-mobile.tsx @@ -1,10 +1,9 @@ import type { HTMLAttributes } from 'react'; import { Trans } from '@lingui/react/macro'; -import { CreditCard, Lock, User, Users } from 'lucide-react'; +import { Lock, User, Users } from 'lucide-react'; import { Link, useLocation } from 'react-router'; -import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; @@ -13,8 +12,6 @@ export type SettingsMobileNavProps = HTMLAttributes; export const SettingsMobileNav = ({ className, ...props }: SettingsMobileNavProps) => { const { pathname } = useLocation(); - const isBillingEnabled = IS_BILLING_ENABLED(); - return (
Security - - {isBillingEnabled && ( - - - - )}
); }; diff --git a/apps/remix/app/components/general/teams/team-billing-portal-button.tsx b/apps/remix/app/components/general/teams/team-billing-portal-button.tsx deleted file mode 100644 index ce71fbc7e..000000000 --- a/apps/remix/app/components/general/teams/team-billing-portal-button.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { msg } from '@lingui/core/macro'; -import { useLingui } from '@lingui/react'; -import { Trans } from '@lingui/react/macro'; - -import { trpc } from '@documenso/trpc/react'; -import { Button } from '@documenso/ui/primitives/button'; -import { useToast } from '@documenso/ui/primitives/use-toast'; - -export type TeamBillingPortalButtonProps = { - buttonProps?: React.ComponentProps; - teamId: number; -}; - -export const TeamBillingPortalButton = ({ buttonProps, teamId }: TeamBillingPortalButtonProps) => { - const { _ } = useLingui(); - const { toast } = useToast(); - - const { mutateAsync: createBillingPortal, isPending } = - trpc.team.createBillingPortal.useMutation(); - - const handleCreatePortal = async () => { - try { - const sessionUrl = await createBillingPortal({ teamId }); - - window.open(sessionUrl, '_blank'); - } catch (err) { - toast({ - title: _(msg`Something went wrong`), - description: _( - msg`We are unable to proceed to the billing portal at this time. Please try again, or contact support.`, - ), - variant: 'destructive', - duration: 10000, - }); - } - }; - - return ( - - ); -}; diff --git a/apps/remix/app/components/general/teams/team-email-dropdown.tsx b/apps/remix/app/components/general/teams/team-email-dropdown.tsx index 3c9be2dfb..cbc10228a 100644 --- a/apps/remix/app/components/general/teams/team-email-dropdown.tsx +++ b/apps/remix/app/components/general/teams/team-email-dropdown.tsx @@ -25,7 +25,7 @@ export const TeamEmailDropdown = ({ team }: TeamEmailDropdownProps) => { const { toast } = useToast(); const { mutateAsync: resendEmailVerification, isPending: isResendingEmailVerification } = - trpc.team.resendTeamEmailVerification.useMutation({ + trpc.team.email.verification.resend.useMutation({ onSuccess: () => { toast({ title: _(msg`Success`), diff --git a/apps/remix/app/components/general/teams/team-email-usage.tsx b/apps/remix/app/components/general/teams/team-email-usage.tsx index 5ae8eb1d6..6e93b6a06 100644 --- a/apps/remix/app/components/general/teams/team-email-usage.tsx +++ b/apps/remix/app/components/general/teams/team-email-usage.tsx @@ -30,7 +30,7 @@ export const TeamEmailUsage = ({ teamEmail }: TeamEmailUsageProps) => { const { toast } = useToast(); const { mutateAsync: deleteTeamEmail, isPending: isDeletingTeamEmail } = - trpc.team.deleteTeamEmail.useMutation({ + trpc.team.email.delete.useMutation({ onSuccess: () => { toast({ title: _(msg`Success`), diff --git a/apps/remix/app/components/general/template/template-edit-form.tsx b/apps/remix/app/components/general/template/template-edit-form.tsx index 8648444d6..193b0f82e 100644 --- a/apps/remix/app/components/general/template/template-edit-form.tsx +++ b/apps/remix/app/components/general/template/template-edit-form.tsx @@ -31,7 +31,6 @@ import { useCurrentTeam } from '~/providers/team'; export type TemplateEditFormProps = { className?: string; initialTemplate: TTemplate; - isEnterprise: boolean; templateRootPath: string; }; @@ -41,7 +40,6 @@ const EditTemplateSteps: EditTemplateStep[] = ['settings', 'signers', 'fields']; export const TemplateEditForm = ({ initialTemplate, className, - isEnterprise, templateRootPath, }: TemplateEditFormProps) => { const { _ } = useLingui(); @@ -261,7 +259,6 @@ export const TemplateEditForm = ({ recipients={recipients} fields={fields} onSubmit={onAddSettingsFormSubmit} - isEnterprise={isEnterprise} isDocumentPdfLoaded={isDocumentPdfLoaded} /> @@ -274,7 +271,6 @@ export const TemplateEditForm = ({ allowDictateNextSigner={template.templateMeta?.allowDictateNextSigner} templateDirectLink={template.directLink} onSubmit={onAddTemplatePlaceholderFormSubmit} - isEnterprise={isEnterprise} isDocumentPdfLoaded={isDocumentPdfLoaded} /> diff --git a/apps/remix/app/components/tables/admin-claims-table.tsx b/apps/remix/app/components/tables/admin-claims-table.tsx new file mode 100644 index 000000000..425472d0a --- /dev/null +++ b/apps/remix/app/components/tables/admin-claims-table.tsx @@ -0,0 +1,199 @@ +import { useMemo } from 'react'; + +import { useLingui } from '@lingui/react/macro'; +import { Trans } from '@lingui/react/macro'; +import { EditIcon, MoreHorizontalIcon, Trash2Icon } from 'lucide-react'; +import { Link, useSearchParams } from 'react-router'; + +import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params'; +import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params'; +import { SUBSCRIPTION_CLAIM_FEATURE_FLAGS } from '@documenso/lib/types/subscription'; +import { trpc } from '@documenso/trpc/react'; +import { CopyTextButton } from '@documenso/ui/components/common/copy-text-button'; +import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table'; +import { DataTable } from '@documenso/ui/primitives/data-table'; +import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuTrigger, +} from '@documenso/ui/primitives/dropdown-menu'; +import { Skeleton } from '@documenso/ui/primitives/skeleton'; +import { TableCell } from '@documenso/ui/primitives/table'; +import { useToast } from '@documenso/ui/primitives/use-toast'; + +import { ClaimDeleteDialog } from '../dialogs/claim-delete-dialog'; +import { ClaimUpdateDialog } from '../dialogs/claim-update-dialog'; + +export const AdminClaimsTable = () => { + const { t } = useLingui(); + const { toast } = useToast(); + + const [searchParams] = useSearchParams(); + const updateSearchParams = useUpdateSearchParams(); + + const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? [])); + + const { data, isLoading, isLoadingError } = trpc.admin.claims.find.useQuery({ + query: parsedSearchParams.query, + page: parsedSearchParams.page, + perPage: parsedSearchParams.perPage, + }); + + const onPaginationChange = (page: number, perPage: number) => { + updateSearchParams({ + page, + perPage, + }); + }; + + const results = data ?? { + data: [], + perPage: 10, + currentPage: 1, + totalPages: 1, + }; + + const columns = useMemo(() => { + return [ + { + header: t`ID`, + accessorKey: 'id', + maxSize: 50, + cell: ({ row }) => ( + toast({ title: t`ID copied to clipboard` })} + /> + ), + }, + { + header: t`Name`, + accessorKey: 'name', + cell: ({ row }) => ( + + {row.original.name} + + ), + }, + { + header: t`Allowed teams`, + accessorKey: 'teamCount', + cell: ({ row }) => { + if (row.original.teamCount === 0) { + return

{t`Unlimited`}

; + } + + return

{row.original.teamCount}

; + }, + }, + { + header: t`Feature Flags`, + cell: ({ row }) => { + const flags = Object.values(SUBSCRIPTION_CLAIM_FEATURE_FLAGS).filter( + ({ key }) => row.original.flags[key], + ); + + if (flags.length === 0) { + return

{t`None`}

; + } + + return ( +
    + {flags.map(({ key, label }) => ( +
  • {label}
  • + ))} +
+ ); + }, + }, + { + id: 'actions', + cell: ({ row }) => ( + + + + + + + + Actions + + + e.preventDefault()}> +
+ + Update +
+ + } + /> + + e.preventDefault()}> +
+ + Delete +
+ + } + /> +
+
+ ), + }, + ] satisfies DataTableColumnDef<(typeof results)['data'][number]>[]; + }, []); + + return ( +
+ + + + + + + + + + + + + + +
+ +
+
+ + ), + }} + > + {(table) => } +
+
+ ); +}; diff --git a/apps/remix/app/components/tables/admin-dashboard-users-table.tsx b/apps/remix/app/components/tables/admin-dashboard-users-table.tsx index b5b7737f9..6c3919b4c 100644 --- a/apps/remix/app/components/tables/admin-dashboard-users-table.tsx +++ b/apps/remix/app/components/tables/admin-dashboard-users-table.tsx @@ -35,7 +35,6 @@ type AdminDashboardUsersTableProps = { totalPages: number; perPage: number; page: number; - individualPriceIds: string[]; }; export const AdminDashboardUsersTable = ({ @@ -43,7 +42,6 @@ export const AdminDashboardUsersTable = ({ totalPages, perPage, page, - individualPriceIds, }: AdminDashboardUsersTableProps) => { const { _ } = useLingui(); @@ -74,17 +72,6 @@ export const AdminDashboardUsersTable = ({ accessorKey: 'roles', cell: ({ row }) => row.original.roles.join(', '), }, - { - header: _(msg`Subscription`), - accessorKey: 'subscription', - cell: ({ row }) => { - const foundIndividualSubscription = (row.original.subscriptions ?? []).find((sub) => - individualPriceIds.includes(sub.priceId), - ); - - return foundIndividualSubscription?.status ?? 'NONE'; - }, - }, { header: _(msg`Documents`), accessorKey: 'documents', @@ -107,7 +94,7 @@ export const AdminDashboardUsersTable = ({ }, }, ] satisfies DataTableColumnDef<(typeof users)[number]>[]; - }, [individualPriceIds]); + }, []); useEffect(() => { startTransition(() => { diff --git a/apps/remix/app/components/tables/admin-organisations-table.tsx b/apps/remix/app/components/tables/admin-organisations-table.tsx new file mode 100644 index 000000000..37b507faf --- /dev/null +++ b/apps/remix/app/components/tables/admin-organisations-table.tsx @@ -0,0 +1,179 @@ +import { useMemo } from 'react'; + +import { useLingui } from '@lingui/react/macro'; +import { Trans } from '@lingui/react/macro'; +import { + CreditCardIcon, + ExternalLinkIcon, + MoreHorizontalIcon, + SettingsIcon, + UserIcon, +} from 'lucide-react'; +import { Link, useSearchParams } from 'react-router'; + +import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params'; +import { SUBSCRIPTION_STATUS_MAP } from '@documenso/lib/constants/billing'; +import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params'; +import { trpc } from '@documenso/trpc/react'; +import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table'; +import { DataTable } from '@documenso/ui/primitives/data-table'; +import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuTrigger, +} from '@documenso/ui/primitives/dropdown-menu'; +import { Skeleton } from '@documenso/ui/primitives/skeleton'; +import { TableCell } from '@documenso/ui/primitives/table'; + +export const AdminOrganisationsTable = () => { + const { t, i18n } = useLingui(); + + const [searchParams] = useSearchParams(); + const updateSearchParams = useUpdateSearchParams(); + + const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? [])); + + const { data, isLoading, isLoadingError } = trpc.admin.organisation.find.useQuery({ + query: parsedSearchParams.query, + page: parsedSearchParams.page, + perPage: parsedSearchParams.perPage, + }); + + const onPaginationChange = (page: number, perPage: number) => { + updateSearchParams({ + page, + perPage, + }); + }; + + const results = data ?? { + data: [], + perPage: 10, + currentPage: 1, + totalPages: 1, + }; + + const columns = useMemo(() => { + return [ + { + header: t`Organisation`, + accessorKey: 'name', + cell: ({ row }) => ( + {row.original.name} + ), + }, + { + header: t`Created At`, + accessorKey: 'createdAt', + cell: ({ row }) => i18n.date(row.original.createdAt), + }, + { + header: t`Owner`, + accessorKey: 'owner', + cell: ({ row }) => ( + {row.original.owner.name} + ), + }, + { + header: t`Subscription`, + cell: ({ row }) => + row.original.subscription ? ( + + {SUBSCRIPTION_STATUS_MAP[row.original.subscription.status]} + + + ) : ( + 'None' + ), + }, + { + id: 'actions', + cell: ({ row }) => ( + + + + + + + + Actions + + + + + + Manage + + + + + + + View owner + + + + + + + Stripe + {!row.original.customerId &&  (N/A)} + + + + + ), + }, + ] satisfies DataTableColumnDef<(typeof results)['data'][number]>[]; + }, []); + + return ( +
+ + + + + + + + + + + + + + +
+ +
+
+ + ), + }} + > + {(table) => } +
+
+ ); +}; diff --git a/apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx b/apps/remix/app/components/tables/organisation-billing-invoices-table.tsx similarity index 73% rename from apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx rename to apps/remix/app/components/tables/organisation-billing-invoices-table.tsx index 14e843c36..43af226df 100644 --- a/apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +++ b/apps/remix/app/components/tables/organisation-billing-invoices-table.tsx @@ -2,7 +2,7 @@ import { useMemo } from 'react'; import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; -import { Plural, Trans } from '@lingui/react/macro'; +import { Trans } from '@lingui/react/macro'; import { File } from 'lucide-react'; import { DateTime } from 'luxon'; import { Link } from 'react-router'; @@ -11,22 +11,23 @@ import { trpc } from '@documenso/trpc/react'; import { Button } from '@documenso/ui/primitives/button'; import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table'; import { DataTable } from '@documenso/ui/primitives/data-table'; -import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination'; import { Skeleton } from '@documenso/ui/primitives/skeleton'; import { TableCell } from '@documenso/ui/primitives/table'; -export type TeamSettingsBillingInvoicesTableProps = { - teamId: number; +export type OrganisationBillingInvoicesTableProps = { + organisationId: string; + subscriptionExists: boolean; }; -export const TeamSettingsBillingInvoicesTable = ({ - teamId, -}: TeamSettingsBillingInvoicesTableProps) => { +export const OrganisationBillingInvoicesTable = ({ + organisationId, + subscriptionExists, +}: OrganisationBillingInvoicesTableProps) => { const { _ } = useLingui(); - const { data, isLoading, isLoadingError } = trpc.team.findTeamInvoices.useQuery( + const { data, isLoading, isLoadingError } = trpc.billing.invoices.get.useQuery( { - teamId, + organisationId, }, { placeholderData: (previousData) => previousData, @@ -43,7 +44,7 @@ export const TeamSettingsBillingInvoicesTable = ({ }; const results = { - data: data?.data ?? [], + data: data || [], perPage: 100, currentPage: 1, totalPages: 1, @@ -58,13 +59,8 @@ export const TeamSettingsBillingInvoicesTable = ({
-
- - {DateTime.fromSeconds(row.original.created).toFormat('MMMM yyyy')} - - - - +
+ {DateTime.fromSeconds(row.original.created).toFormat('MMMM yyyy')}
), @@ -73,10 +69,10 @@ export const TeamSettingsBillingInvoicesTable = ({ header: _(msg`Status`), accessorKey: 'status', cell: ({ row }) => { - const { status, paid } = row.original; + const { status } = row.original; if (!status) { - return paid ? Paid : Unpaid; + return 'N/A'; } return status.charAt(0).toUpperCase() + status.slice(1); @@ -94,9 +90,9 @@ export const TeamSettingsBillingInvoicesTable = ({ @@ -104,9 +100,9 @@ export const TeamSettingsBillingInvoicesTable = ({ @@ -116,6 +112,10 @@ export const TeamSettingsBillingInvoicesTable = ({ ] satisfies DataTableColumnDef<(typeof results)['data'][number]>[]; }, []); + if (results.data.length === 0 && !subscriptionExists) { + return null; + } + return ( - {(table) => } + {/* {(table) => } */} ); }; diff --git a/apps/remix/app/components/tables/organisation-groups-table.tsx b/apps/remix/app/components/tables/organisation-groups-table.tsx index 6a83e0706..fe5b1b206 100644 --- a/apps/remix/app/components/tables/organisation-groups-table.tsx +++ b/apps/remix/app/components/tables/organisation-groups-table.tsx @@ -7,6 +7,7 @@ import { OrganisationGroupType } from '@prisma/client'; import { Link, useSearchParams } from 'react-router'; import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params'; +import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; import { EXTENDED_ORGANISATION_MEMBER_ROLE_MAP } from '@documenso/lib/constants/organisations'; import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params'; import { trpc } from '@documenso/trpc/react'; @@ -17,8 +18,6 @@ import { DataTablePagination } from '@documenso/ui/primitives/data-table-paginat import { Skeleton } from '@documenso/ui/primitives/skeleton'; import { TableCell } from '@documenso/ui/primitives/table'; -import { useCurrentOrganisation } from '~/providers/organisation'; - import { OrganisationGroupDeleteDialog } from '../dialogs/organisation-group-delete-dialog'; export const OrganisationGroupsDataTable = () => { diff --git a/apps/remix/app/components/tables/organisation-member-invites-table.tsx b/apps/remix/app/components/tables/organisation-member-invites-table.tsx index b2e24fb30..8813994e0 100644 --- a/apps/remix/app/components/tables/organisation-member-invites-table.tsx +++ b/apps/remix/app/components/tables/organisation-member-invites-table.tsx @@ -8,6 +8,7 @@ import { History, MoreHorizontal, Trash2 } from 'lucide-react'; import { useSearchParams } from 'react-router'; import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params'; +import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; import { ORGANISATION_MEMBER_ROLE_MAP } from '@documenso/lib/constants/organisations'; import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params'; import { trpc } from '@documenso/trpc/react'; @@ -26,8 +27,6 @@ import { Skeleton } from '@documenso/ui/primitives/skeleton'; import { TableCell } from '@documenso/ui/primitives/table'; import { useToast } from '@documenso/ui/primitives/use-toast'; -import { useCurrentOrganisation } from '~/providers/organisation'; - export const OrganisationMemberInvitesTable = () => { const [searchParams] = useSearchParams(); const updateSearchParams = useUpdateSearchParams(); diff --git a/apps/remix/app/components/tables/organisation-members-table.tsx b/apps/remix/app/components/tables/organisation-members-table.tsx index 07f6cea46..749810a8e 100644 --- a/apps/remix/app/components/tables/organisation-members-table.tsx +++ b/apps/remix/app/components/tables/organisation-members-table.tsx @@ -8,6 +8,7 @@ import { Edit, MoreHorizontal, Trash2 } from 'lucide-react'; import { useSearchParams } from 'react-router'; import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params'; +import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; import { ORGANISATION_MEMBER_ROLE_MAP } from '@documenso/lib/constants/organisations'; import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params'; import { isOrganisationRoleWithinUserHierarchy } from '@documenso/lib/utils/organisations'; @@ -29,7 +30,6 @@ import { TableCell } from '@documenso/ui/primitives/table'; import { OrganisationMemberDeleteDialog } from '~/components/dialogs/organisation-member-delete-dialog'; import { OrganisationMemberUpdateDialog } from '~/components/dialogs/organisation-member-update-dialog'; -import { useCurrentOrganisation } from '~/providers/organisation'; export const OrganisationMembersDataTable = () => { const { _, i18n } = useLingui(); diff --git a/apps/remix/app/components/tables/organisation-pending-teams-table.tsx b/apps/remix/app/components/tables/organisation-pending-teams-table.tsx deleted file mode 100644 index 38ea8d27c..000000000 --- a/apps/remix/app/components/tables/organisation-pending-teams-table.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import { useEffect, useMemo, useState } from 'react'; - -import { msg } from '@lingui/core/macro'; -import { useLingui } from '@lingui/react'; -import { useSearchParams } from 'react-router'; - -import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params'; -import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; -import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params'; -import { trpc } from '@documenso/trpc/react'; -import { AvatarWithText } from '@documenso/ui/primitives/avatar'; -import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table'; -import { DataTable } from '@documenso/ui/primitives/data-table'; -import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination'; -import { Skeleton } from '@documenso/ui/primitives/skeleton'; -import { TableCell } from '@documenso/ui/primitives/table'; - -import { TeamCheckoutCreateDialog } from '~/components/dialogs/team-checkout-create-dialog'; - -import { UserSettingsPendingTeamsTableActions } from './user-settings-pending-teams-table-actions'; - -export const OrganisationPendingTeamsTable = () => { - const { _, i18n } = useLingui(); - - const [searchParams] = useSearchParams(); - const updateSearchParams = useUpdateSearchParams(); - - const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? [])); - - const [checkoutPendingTeamId, setCheckoutPendingTeamId] = useState(null); - - const { data, isLoading, isLoadingError } = trpc.team.findTeamsPending.useQuery( - { - query: parsedSearchParams.query, - page: parsedSearchParams.page, - perPage: parsedSearchParams.perPage, - }, - { - placeholderData: (previousData) => previousData, - }, - ); - - const onPaginationChange = (page: number, perPage: number) => { - updateSearchParams({ - page, - perPage, - }); - }; - - const results = data ?? { - data: [], - perPage: 10, - currentPage: 1, - totalPages: 1, - }; - - const columns = useMemo(() => { - return [ - { - header: _(msg`Team`), - accessorKey: 'name', - cell: ({ row }) => ( - {row.original.name} - } - secondaryText={`${NEXT_PUBLIC_WEBAPP_URL()}/t/${row.original.url}`} - /> - ), - }, - { - header: _(msg`Created on`), - accessorKey: 'createdAt', - cell: ({ row }) => i18n.date(row.original.createdAt), - }, - { - id: 'actions', - cell: ({ row }) => ( - - ), - }, - ] satisfies DataTableColumnDef<(typeof results)['data'][number]>[]; - }, []); - - useEffect(() => { - const searchParamCheckout = searchParams?.get('checkout'); - - if (searchParamCheckout && !isNaN(parseInt(searchParamCheckout))) { - setCheckoutPendingTeamId(parseInt(searchParamCheckout)); - updateSearchParams({ checkout: null }); - } - }, [searchParams, updateSearchParams]); - - return ( - <> - - -
- - -
- - -
-
-
- - - - -
- - -
-
- - ), - }} - > - {(table) => - results.totalPages > 1 && ( - - ) - } -
- - setCheckoutPendingTeamId(null)} - /> - - ); -}; diff --git a/apps/remix/app/components/tables/organisation-teams-table.tsx b/apps/remix/app/components/tables/organisation-teams-table.tsx index 86cd544b7..ff5085a93 100644 --- a/apps/remix/app/components/tables/organisation-teams-table.tsx +++ b/apps/remix/app/components/tables/organisation-teams-table.tsx @@ -7,6 +7,7 @@ import { useSearchParams } from 'react-router'; import { Link } from 'react-router'; import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params'; +import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params'; import { formatAvatarUrl } from '@documenso/lib/utils/avatars'; @@ -19,8 +20,6 @@ import { DataTablePagination } from '@documenso/ui/primitives/data-table-paginat import { Skeleton } from '@documenso/ui/primitives/skeleton'; import { TableCell } from '@documenso/ui/primitives/table'; -import { useCurrentOrganisation } from '~/providers/organisation'; - import { TeamDeleteDialog } from '../dialogs/team-delete-dialog'; export const OrganisationTeamsTable = () => { diff --git a/apps/remix/app/components/tables/team-members-table.tsx b/apps/remix/app/components/tables/team-members-table.tsx index ebf31e2e8..39a9b3afd 100644 --- a/apps/remix/app/components/tables/team-members-table.tsx +++ b/apps/remix/app/components/tables/team-members-table.tsx @@ -7,6 +7,7 @@ import { EditIcon, MoreHorizontal, Trash2Icon } from 'lucide-react'; import { useSearchParams } from 'react-router'; import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params'; +import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; import { EXTENDED_TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams'; import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params'; import { extractInitials } from '@documenso/lib/utils/recipient-formatter'; @@ -26,14 +27,13 @@ import { import { Skeleton } from '@documenso/ui/primitives/skeleton'; import { TableCell } from '@documenso/ui/primitives/table'; -import { useCurrentOrganisation } from '~/providers/organisation'; import { useCurrentTeam } from '~/providers/team'; import { TeamMemberDeleteDialog } from '../dialogs/team-member-delete-dialog'; import { TeamMemberUpdateDialog } from '../dialogs/team-member-update-dialog'; -export const TeamMembersDataTable = () => { - const { _, i18n } = useLingui(); +export const TeamMembersTable = () => { + const { _ } = useLingui(); const [searchParams] = useSearchParams(); const updateSearchParams = useUpdateSearchParams(); diff --git a/apps/remix/app/components/tables/user-settings-organisations-table.tsx b/apps/remix/app/components/tables/user-organisations-table.tsx similarity index 87% rename from apps/remix/app/components/tables/user-settings-organisations-table.tsx rename to apps/remix/app/components/tables/user-organisations-table.tsx index 613ee2abc..5b27c3f37 100644 --- a/apps/remix/app/components/tables/user-settings-organisations-table.tsx +++ b/apps/remix/app/components/tables/user-organisations-table.tsx @@ -3,8 +3,7 @@ import { useMemo } from 'react'; import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; -import { Link, useSearchParams } from 'react-router'; -import { useLocation } from 'react-router'; +import { Link } from 'react-router'; import { useSession } from '@documenso/lib/client-only/providers/session'; import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; @@ -21,14 +20,10 @@ import { TableCell } from '@documenso/ui/primitives/table'; import { OrganisationLeaveDialog } from '../dialogs/organisation-leave-dialog'; -export const UserSettingsOrganisationsTable = () => { +export const UserOrganisationsTable = () => { const { _, i18n } = useLingui(); const { user } = useSession(); - const [searchParams, setSearchParams] = useSearchParams(); - - const { pathname } = useLocation(); - const { data, isLoading, isLoadingError } = trpc.organisation.getMany.useQuery(); const results = { @@ -38,14 +33,6 @@ export const UserSettingsOrganisationsTable = () => { totalPages: 1, }; - // Todo: Orgs - // const results = data ?? { - // data: [], - // perPage: 10, - // currentPage: 1, - // totalPages: 1, - // }; - const columns = useMemo(() => { return [ { @@ -97,7 +84,6 @@ export const UserSettingsOrganisationsTable = () => { organisationId={row.original.id} organisationName={row.original.name} organisationAvatarImageId={row.original.avatarImageId} - organisationMemberId={row.original.currentMemberId} role={row.original.currentOrganisationRole} trigger={
); }; diff --git a/apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx b/apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx deleted file mode 100644 index f57edfb96..000000000 --- a/apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { msg } from '@lingui/core/macro'; -import { useLingui } from '@lingui/react'; -import { Trans } from '@lingui/react/macro'; - -import { trpc } from '@documenso/trpc/react'; -import { cn } from '@documenso/ui/lib/utils'; -import { Button } from '@documenso/ui/primitives/button'; -import { useToast } from '@documenso/ui/primitives/use-toast'; - -export type UserSettingsPendingTeamsTableActionsProps = { - className?: string; - pendingTeamId: number; - onPayClick: (pendingTeamId: number) => void; -}; - -export const UserSettingsPendingTeamsTableActions = ({ - className, - pendingTeamId, - onPayClick, -}: UserSettingsPendingTeamsTableActionsProps) => { - const { _ } = useLingui(); - const { toast } = useToast(); - - const { mutateAsync: deleteTeamPending, isPending: deletingTeam } = - trpc.team.deleteTeamPending.useMutation({ - onSuccess: () => { - toast({ - title: _(msg`Success`), - description: _(msg`Pending team deleted.`), - }); - }, - onError: () => { - toast({ - title: _(msg`Something went wrong`), - description: _( - msg`We encountered an unknown error while attempting to delete the pending team. Please try again later.`, - ), - duration: 10000, - variant: 'destructive', - }); - }, - }); - - return ( -
- - - -
- ); -}; diff --git a/apps/remix/app/routes/_authenticated+/_layout.tsx b/apps/remix/app/routes/_authenticated+/_layout.tsx index 01aaee38a..39835fd91 100644 --- a/apps/remix/app/routes/_authenticated+/_layout.tsx +++ b/apps/remix/app/routes/_authenticated+/_layout.tsx @@ -3,18 +3,14 @@ import { Trans } from '@lingui/react/macro'; import { Link, Outlet, redirect } from 'react-router'; import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session'; -import { getLimits } from '@documenso/ee/server-only/limits/client'; import { LimitsProvider } from '@documenso/ee/server-only/limits/provider/client'; +import { OrganisationProvider } from '@documenso/lib/client-only/providers/organisation'; import { useSession } from '@documenso/lib/client-only/providers/session'; -import { getSiteSettings } from '@documenso/lib/server-only/site-settings/get-site-settings'; -import { SITE_SETTINGS_BANNER_ID } from '@documenso/lib/server-only/site-settings/schemas/banner'; import { Button } from '@documenso/ui/primitives/button'; -import { AppBanner } from '~/components/general/app-banner'; import { Header } from '~/components/general/app-header'; import { GenericErrorLayout } from '~/components/general/generic-error-layout'; import { VerifyEmailBanner } from '~/components/general/verify-email-banner'; -import { OrganisationProvider } from '~/providers/organisation'; import { TeamProvider } from '~/providers/team'; import type { Route } from './+types/_layout'; @@ -35,23 +31,23 @@ export async function loader({ request }: Route.LoaderArgs) { throw redirect('/signin'); } - const [limits, banner] = await Promise.all([ - getLimits({ headers: requestHeaders }), - getSiteSettings().then((settings) => - settings.find((setting) => setting.id === SITE_SETTINGS_BANNER_ID), - ), - ]); + // const [limits, banner] = await Promise.all([ + // getLimits({ headers: requestHeaders }), + // getSiteSettings().then((settings) => + // settings.find((setting) => setting.id === SITE_SETTINGS_BANNER_ID), + // ), + // ]); return { - banner, - limits, + // banner, + // limits, }; } export default function Layout({ loaderData, params }: Route.ComponentProps) { const { user, organisations } = useSession(); - const { banner, limits } = loaderData; + // const { banner, limits } = loaderData; const teamUrl = params.teamUrl; const orgUrl = params.orgUrl; @@ -140,12 +136,12 @@ export default function Layout({ loaderData, params }: Route.ComponentProps) { return ( - +
{!user.emailVerified && } - {banner && } + {/* {banner && } */}
diff --git a/apps/remix/app/routes/_authenticated+/admin+/_layout.tsx b/apps/remix/app/routes/_authenticated+/admin+/_layout.tsx index 6e7cb050b..1127d2672 100644 --- a/apps/remix/app/routes/_authenticated+/admin+/_layout.tsx +++ b/apps/remix/app/routes/_authenticated+/admin+/_layout.tsx @@ -1,5 +1,13 @@ import { Trans } from '@lingui/react/macro'; -import { BarChart3, FileStack, Settings, Trophy, Users, Wallet2 } from 'lucide-react'; +import { + BarChart3, + Building2Icon, + FileStack, + Settings, + Trophy, + Users, + Wallet2, +} from 'lucide-react'; import { Link, Outlet, redirect, useLocation } from 'react-router'; import { getSession } from '@documenso/auth/server/lib/utils/get-session'; @@ -21,8 +29,12 @@ export default function AdminLayout() { const { pathname } = useLocation(); return ( -
-
+
+

+ Admin Panel +

+ +
+ + + + - - + } + secondaryButton={null} + /> + ); + } + + return ( +
+ + + + + + + +
+ + Subscription + + + + {organisation.subscription ? ( + + {SUBSCRIPTION_STATUS_MAP[organisation.subscription.status]} subscription found + + ) : ( + No subscription found + )} + +
+ + {!organisation.customerId && ( +
+ +
+ )} + + {organisation.customerId && !organisation.subscription && ( +
+ +
+ )} + + {organisation.subscription && ( +
+ +
+ )} +
+ + + +
+
+ + +
+ +
+
+ +
+ + +
+ +
+
+
+
+ ); +} + +const ZUpdateGenericOrganisationDataFormSchema = + ZUpdateAdminOrganisationRequestSchema.shape.data.pick({ + name: true, + url: true, + }); + +type TUpdateGenericOrganisationDataFormSchema = z.infer< + typeof ZUpdateGenericOrganisationDataFormSchema +>; + +type OrganisationAdminFormOptions = { + organisation: TGetAdminOrganisationResponse; +}; + +const GenericOrganisationAdminForm = ({ organisation }: OrganisationAdminFormOptions) => { + const { toast } = useToast(); + const { t } = useLingui(); + + const { mutateAsync: updateOrganisation } = trpc.admin.organisation.update.useMutation(); + + const form = useForm({ + resolver: zodResolver(ZUpdateGenericOrganisationDataFormSchema), + defaultValues: { + name: organisation.name, + url: organisation.url, + }, + }); + + const onSubmit = async (data: TUpdateGenericOrganisationDataFormSchema) => { + try { + await updateOrganisation({ + organisationId: organisation.id, + data, + }); + + toast({ + title: t`Success`, + description: t`Organisation has been updated successfully`, + duration: 5000, + }); + } catch (err) { + const error = AppError.parseError(err); + console.error(error); + + toast({ + title: t`An error occurred`, + description: t`We couldn't update the organisation. Please try again.`, + variant: 'destructive', + }); + } + }; + + return ( +
+ + ( + + + Organisation Name + + + + + + + )} + /> + + ( + + + Organisation URL + + + + + {!form.formState.errors.url && ( + + {field.value ? ( + `${NEXT_PUBLIC_WEBAPP_URL()}/org/${field.value}` + ) : ( + A unique URL to identify the organisation + )} + + )} + + + + )} + /> + +
+ +
+ + + ); +}; + +const ZUpdateOrganisationBillingFormSchema = ZUpdateAdminOrganisationRequestSchema.shape.data.pick({ + claims: true, + customerId: true, + originalSubscriptionClaimId: true, +}); + +type TUpdateOrganisationBillingFormSchema = z.infer; + +const OrganisationAdminForm = ({ organisation }: OrganisationAdminFormOptions) => { + const { toast } = useToast(); + const { t } = useLingui(); + + const { mutateAsync: updateOrganisation } = trpc.admin.organisation.update.useMutation(); + + const form = useForm({ + resolver: zodResolver(ZUpdateOrganisationBillingFormSchema), + defaultValues: { + customerId: organisation.customerId || '', + claims: { + teamCount: organisation.organisationClaim.teamCount, + memberCount: organisation.organisationClaim.memberCount, + flags: organisation.organisationClaim.flags, + }, + originalSubscriptionClaimId: organisation.organisationClaim.originalSubscriptionClaimId || '', + }, + }); + + const onSubmit = async (values: TUpdateOrganisationBillingFormSchema) => { + try { + await updateOrganisation({ + organisationId: organisation.id, + data: values, + }); + + toast({ + title: t`Success`, + description: t`Organisation has been updated successfully`, + duration: 5000, + }); + } catch (err) { + const error = AppError.parseError(err); + console.error(error); + + toast({ + title: t`An error occurred`, + description: t`We couldn't update the organisation. Please try again.`, + variant: 'destructive', + }); + } + }; + + return ( +
+ + ( + + + Inherited subscription claim + + + + + + +

+ + Inherited subscription claim + +

+ +

+ + This is the claim that this organisation was initially created with. Any + feature flag changes to this claim will be backported into this + organisation. + +

+ +

+ + For example, if the claim has a new flag "FLAG_1" set to true, then this + organisation will get that flag added. + +

+

+ + This will ONLY backport feature flags which are set to true, anything + disabled in the initial claim will not be backported + +

+
+
+
+ + + + +
+ )} + /> + + ( + + + Stripe Customer ID + + + + + {!form.formState.errors.customerId && field.value && ( + + {`https://dashboard.stripe.com/customers/${field.value}`} + + )} + + + + )} + /> + + ( + + + Team Count + + + field.onChange(parseInt(e.target.value, 10) || 0)} + /> + + + Number of teams allowed. 0 = Unlimited + + + + )} + /> + + ( + + + Member Count + + + field.onChange(parseInt(e.target.value, 10) || 0)} + /> + + + Number of members allowed. 0 = Unlimited + + + + )} + /> + +
+ + Feature Flags + + +
+ {Object.values(SUBSCRIPTION_CLAIM_FEATURE_FLAGS).map(({ key, label }) => ( + ( + + +
+ + + +
+
+
+ )} + /> + ))} +
+
+ +
+ +
+ + + ); +}; diff --git a/apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx b/apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx new file mode 100644 index 000000000..b09d3caae --- /dev/null +++ b/apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx @@ -0,0 +1,62 @@ +import { useEffect, useState } from 'react'; + +import { useLingui } from '@lingui/react/macro'; +import { useLocation, useSearchParams } from 'react-router'; + +import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value'; +import { Input } from '@documenso/ui/primitives/input'; + +import { SettingsHeader } from '~/components/general/settings-header'; +import { AdminOrganisationsTable } from '~/components/tables/admin-organisations-table'; + +export default function Organisations() { + const { t } = useLingui(); + + const [searchParams, setSearchParams] = useSearchParams(); + const { pathname } = useLocation(); + + const [searchQuery, setSearchQuery] = useState(() => searchParams?.get('query') ?? ''); + + const debouncedSearchQuery = useDebouncedValue(searchQuery, 500); + + /** + * Handle debouncing the search query. + */ + useEffect(() => { + const params = new URLSearchParams(searchParams?.toString()); + + params.set('query', debouncedSearchQuery); + + if (debouncedSearchQuery === '') { + params.delete('query'); + } + + // If nothing to change then do nothing. + if (params.toString() === searchParams?.toString()) { + return; + } + + setSearchParams(params); + }, [debouncedSearchQuery, pathname, searchParams]); + + return ( +
+ + +
+ setSearchQuery(e.target.value)} + placeholder={t`Search by organisation ID, name, customer ID or owner email`} + className="mb-4" + /> + + +
+
+ ); +} diff --git a/apps/remix/app/routes/_authenticated+/admin+/stats.tsx b/apps/remix/app/routes/_authenticated+/admin+/stats.tsx index b3141e49d..6e54dbaec 100644 --- a/apps/remix/app/routes/_authenticated+/admin+/stats.tsx +++ b/apps/remix/app/routes/_authenticated+/admin+/stats.tsx @@ -18,9 +18,8 @@ import { import { getDocumentStats } from '@documenso/lib/server-only/admin/get-documents-stats'; import { getRecipientsStats } from '@documenso/lib/server-only/admin/get-recipients-stats'; import { - getUserWithSignedDocumentMonthlyGrowth, + getOrganisationsWithSubscriptionsCount, getUsersCount, - getUsersWithSubscriptionsCount, } from '@documenso/lib/server-only/admin/get-users-stats'; import { getSignerConversionMonthly } from '@documenso/lib/server-only/user/get-signer-conversion'; @@ -34,31 +33,31 @@ import type { Route } from './+types/stats'; export async function loader() { const [ usersCount, - usersWithSubscriptionsCount, + organisationsWithSubscriptionsCount, docStats, recipientStats, signerConversionMonthly, // userWithAtLeastOneDocumentPerMonth, // userWithAtLeastOneDocumentSignedPerMonth, - MONTHLY_USERS_SIGNED, + // MONTHLY_USERS_SIGNED, ] = await Promise.all([ getUsersCount(), - getUsersWithSubscriptionsCount(), + getOrganisationsWithSubscriptionsCount(), getDocumentStats(), getRecipientsStats(), getSignerConversionMonthly(), // getUserWithAtLeastOneDocumentPerMonth(), // getUserWithAtLeastOneDocumentSignedPerMonth(), - getUserWithSignedDocumentMonthlyGrowth(), + // getUserWithSignedDocumentMonthlyGrowth(), ]); return { usersCount, - usersWithSubscriptionsCount, + organisationsWithSubscriptionsCount, docStats, recipientStats, signerConversionMonthly, - MONTHLY_USERS_SIGNED, + // MONTHLY_USERS_SIGNED, }; } @@ -67,11 +66,11 @@ export default function AdminStatsPage({ loaderData }: Route.ComponentProps) { const { usersCount, - usersWithSubscriptionsCount, + organisationsWithSubscriptionsCount, docStats, recipientStats, signerConversionMonthly, - MONTHLY_USERS_SIGNED, + // MONTHLY_USERS_SIGNED, } = loaderData; return ( @@ -86,7 +85,7 @@ export default function AdminStatsPage({ loaderData }: Route.ComponentProps) { @@ -149,12 +148,14 @@ export default function AdminStatsPage({ loaderData }: Route.ComponentProps) {
-

- Manage subscriptions -

-
- - - - ID - - Status - - - Created At - - - Ends On - - - User ID - - - - - {subscriptions.map((subscription, index) => ( - - {subscription.id} - {subscription.status} - - {subscription.createdAt - ? new Date(subscription.createdAt).toLocaleDateString(undefined, { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - }) - : 'N/A'} - - - {subscription.periodEnd - ? new Date(subscription.periodEnd).toLocaleDateString(undefined, { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - }) - : 'N/A'} - - - {subscription.userId} - - - ))} - -
-
-
- ); -} diff --git a/apps/remix/app/routes/_authenticated+/admin+/users._index.tsx b/apps/remix/app/routes/_authenticated+/admin+/users._index.tsx index f34bde4af..c661d5a91 100644 --- a/apps/remix/app/routes/_authenticated+/admin+/users._index.tsx +++ b/apps/remix/app/routes/_authenticated+/admin+/users._index.tsx @@ -1,7 +1,5 @@ import { Trans } from '@lingui/react/macro'; -import { getPricesByPlan } from '@documenso/ee/server-only/stripe/get-prices-by-plan'; -import { STRIPE_PLAN_TYPE } from '@documenso/lib/constants/billing'; import { findUsers } from '@documenso/lib/server-only/user/get-all-users'; import { AdminDashboardUsersTable } from '~/components/tables/admin-dashboard-users-table'; @@ -15,24 +13,20 @@ export async function loader({ request }: Route.LoaderArgs) { const perPage = Number(url.searchParams.get('perPage')) || 10; const search = url.searchParams.get('search') || ''; - const [{ users, totalPages }, individualPrices] = await Promise.all([ + const [{ users, totalPages }] = await Promise.all([ findUsers({ username: search, email: search, page, perPage }), - getPricesByPlan([STRIPE_PLAN_TYPE.REGULAR, STRIPE_PLAN_TYPE.COMMUNITY]).catch(() => []), ]); - const individualPriceIds = individualPrices.map((price) => price.id); - return { users, totalPages, - individualPriceIds, page, perPage, }; } export default function AdminManageUsersPage({ loaderData }: Route.ComponentProps) { - const { users, totalPages, individualPriceIds, page, perPage } = loaderData; + const { users, totalPages, page, perPage } = loaderData; return (
@@ -42,7 +36,6 @@ export default function AdminManageUsersPage({ loaderData }: Route.ComponentProp Welcome back! Here's an overview of your account.

+ +
{/* Organisations Section */} diff --git a/apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx b/apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx index 8c9bbf1ab..6ba90fd40 100644 --- a/apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx +++ b/apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx @@ -11,6 +11,7 @@ import { } from 'lucide-react'; import { Link } from 'react-router'; +import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; import { TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams'; import { formatAvatarUrl } from '@documenso/lib/utils/avatars'; import { canExecuteOrganisationAction } from '@documenso/lib/utils/organisations'; @@ -29,7 +30,6 @@ import { import { TeamCreateDialog } from '~/components/dialogs/team-create-dialog'; import { TeamDeleteDialog } from '~/components/dialogs/team-delete-dialog'; -import { useCurrentOrganisation } from '~/providers/organisation'; export default function OrganisationSettingsTeamsPage() { const { t, i18n } = useLingui(); diff --git a/apps/remix/app/routes/_authenticated+/org.$orgUrl._layout.tsx b/apps/remix/app/routes/_authenticated+/org.$orgUrl._layout.tsx index 321b8a45d..4c22e89bc 100644 --- a/apps/remix/app/routes/_authenticated+/org.$orgUrl._layout.tsx +++ b/apps/remix/app/routes/_authenticated+/org.$orgUrl._layout.tsx @@ -3,12 +3,13 @@ import { Outlet } from 'react-router'; import type { Route } from './+types/_layout'; export default function Layout({ params }: Route.ComponentProps) { + // Todo: orgs return (
{/* {currentOrganisation.subscription && currentOrganisation.subscription.status !== SubscriptionStatus.ACTIVE && ( - } secondaryButton={null} - > + /> ); } diff --git a/apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx b/apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx index 6e5b1172b..7820e20a3 100644 --- a/apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +++ b/apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx @@ -1,5 +1,16 @@ +import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; +import { Loader } from 'lucide-react'; +import type Stripe from 'stripe'; +import { match } from 'ts-pattern'; +import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; +import { canExecuteOrganisationAction } from '@documenso/lib/utils/organisations'; +import { trpc } from '@documenso/trpc/react'; + +import { BillingPlans } from '~/components/general/billing-plans'; +import { OrganisationBillingPortalButton } from '~/components/general/organisations/organisation-billing-portal-button'; +import { OrganisationBillingInvoicesTable } from '~/components/tables/organisation-billing-invoices-table'; import { appMetaTags } from '~/utils/meta'; export function meta() { @@ -7,6 +18,36 @@ export function meta() { } export default function TeamsSettingBillingPage() { + const { _, i18n } = useLingui(); + + const organisation = useCurrentOrganisation(); + + const { data: subscriptionQuery, isLoading: isLoadingSubscription } = + trpc.billing.subscription.get.useQuery({ + organisationId: organisation.id, + }); + + if (isLoadingSubscription || !subscriptionQuery) { + return ( +
+ +
+ ); + } + + const { subscription, plans } = subscriptionQuery; + + const canManageBilling = canExecuteOrganisationAction( + 'MANAGE_BILLING', + organisation.currentOrganisationRole, + ); + + const { organisationSubscription, stripeSubscription } = subscription || {}; + + const currentProductName = + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + (stripeSubscription?.items.data[0].price.product as Stripe.Product | undefined)?.name; + return (
@@ -16,120 +57,91 @@ export default function TeamsSettingBillingPage() {
- Billing has been moved to organisations + {!organisationSubscription && ( +

+ + You are currently on the Free Plan. + +

+ )} + + {organisationSubscription && + match(organisationSubscription.status) + .with('ACTIVE', () => ( +

+ {currentProductName ? ( + + You are currently subscribed to{' '} + {currentProductName} + + ) : ( + You currently have an active plan + )} + + {organisationSubscription.periodEnd && ( + + {' '} + which is set to{' '} + {organisationSubscription.cancelAtPeriodEnd ? ( + + end on{' '} + + {i18n.date(organisationSubscription.periodEnd)}. + + + ) : ( + + automatically renew on{' '} + + {i18n.date(organisationSubscription.periodEnd)}. + + + )} + + )} +

+ )) + .with('INACTIVE', () => ( +

+ {currentProductName ? ( + + You currently have an inactive{' '} + {currentProductName} subscription + + ) : ( + Your current plan is inactive. + )} +

+ )) + .with('PAST_DUE', () => ( +

+ {currentProductName ? ( + + Your current {currentProductName} plan is past due. Please update your + payment information. + + ) : ( + Your current plan is past due. + )} +

+ )) + .otherwise(() => null)}
+ +
+ +
+ + {!subscription && canManageBilling && } + +
+ +
); } - -// import { msg } from '@lingui/core/macro'; -// import { useLingui } from '@lingui/react'; -// import { Plural, Trans } from '@lingui/react/macro'; -// import { DateTime } from 'luxon'; -// import type Stripe from 'stripe'; -// import { match } from 'ts-pattern'; - -// import { getSession } from '@documenso/auth/server/lib/utils/get-session'; -// import { stripe } from '@documenso/lib/server-only/stripe'; -// import { getTeamByUrl } from '@documenso/lib/server-only/team/get-team'; -// import { canExecuteTeamAction } from '@documenso/lib/utils/teams'; -// import { Card, CardContent } from '@documenso/ui/primitives/card'; - -// import { SettingsHeader } from '~/components/general/settings-header'; -// import { TeamBillingPortalButton } from '~/components/general/teams/team-billing-portal-button'; -// import { TeamSettingsBillingInvoicesTable } from '~/components/tables/team-settings-billing-invoices-table'; -// import { superLoaderJson, useSuperLoaderData } from '~/utils/super-json-loader'; - -// import type { Route } from './+types/settings.billing'; - -// export async function loader({ request, params }: Route.LoaderArgs) { -// const session = await getSession(request); - -// const team = await getTeamByUrl({ -// userId: session.user.id, -// teamUrl: params.teamUrl, -// }); - -// let teamSubscription: Stripe.Subscription | null = null; - -// if (team.subscription) { -// teamSubscription = await stripe.subscriptions.retrieve(team.subscription.planId); -// } - -// return superLoaderJson({ -// team, -// teamSubscription, -// }); -// } - -// export default function TeamsSettingBillingPage() { -// const { _ } = useLingui(); - -// const { team, teamSubscription } = useSuperLoaderData(); - -// const canManageBilling = canExecuteTeamAction('MANAGE_BILLING', team.currentTeamMember.role); - -// const formatTeamSubscriptionDetails = (subscription: Stripe.Subscription | null) => { -// if (!subscription) { -// return No payment required; -// } - -// const numberOfSeats = subscription.items.data[0].quantity ?? 0; - -// const formattedDate = DateTime.fromSeconds(subscription.current_period_end).toFormat( -// 'LLL dd, yyyy', -// ); - -// const subscriptionInterval = match(subscription?.items.data[0].plan.interval) -// .with('year', () => _(msg`Yearly`)) -// .with('month', () => _(msg`Monthly`)) -// .otherwise(() => _(msg`Unknown`)); - -// return ( -// -// -// {' • '} -// {subscriptionInterval} -// {' • '} -// Renews: {formattedDate} -// -// ); -// }; - -// return ( -//
-// - -// -// -//
-//

-// {formatTeamSubscriptionDetails(teamSubscription)} -//

-//
- -// {teamSubscription && ( -//
-// -//
-// )} -//
-//
- -//
-// -//
-//
-// ); -// } diff --git a/apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.general.tsx b/apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.general.tsx index a188b4269..86ed5541d 100644 --- a/apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.general.tsx +++ b/apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.general.tsx @@ -2,6 +2,7 @@ import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; +import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; import { canExecuteOrganisationAction } from '@documenso/lib/utils/organisations'; import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert'; @@ -9,7 +10,6 @@ import { OrganisationDeleteDialog } from '~/components/dialogs/organisation-dele import { AvatarImageForm } from '~/components/forms/avatar-image'; import { OrganisationUpdateForm } from '~/components/forms/organisation-update-form'; import { SettingsHeader } from '~/components/general/settings-header'; -import { useCurrentOrganisation } from '~/providers/organisation'; import { appMetaTags } from '~/utils/meta'; export function meta() { diff --git a/apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx b/apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx index 63459ffc3..94393a8ad 100644 --- a/apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +++ b/apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx @@ -10,6 +10,7 @@ import { useForm } from 'react-hook-form'; import { Link } from 'react-router'; import { z } from 'zod'; +import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; import { ORGANISATION_MEMBER_ROLE_HIERARCHY, ORGANISATION_MEMBER_ROLE_MAP, @@ -44,7 +45,6 @@ import { useToast } from '@documenso/ui/primitives/use-toast'; import { OrganisationGroupDeleteDialog } from '~/components/dialogs/organisation-group-delete-dialog'; import { GenericErrorLayout } from '~/components/general/generic-error-layout'; import { SettingsHeader } from '~/components/general/settings-header'; -import { useCurrentOrganisation } from '~/providers/organisation'; import type { Route } from './+types/org.$orgUrl.settings.groups.$id'; diff --git a/apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx b/apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx index e309a3539..8d026ae64 100644 --- a/apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx +++ b/apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx @@ -2,6 +2,7 @@ import { Trans, useLingui } from '@lingui/react/macro'; import { Loader } from 'lucide-react'; import { Link } from 'react-router'; +import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; import { DocumentSignatureType } from '@documenso/lib/constants/document'; import { putFile } from '@documenso/lib/universal/upload/put-file'; import { canExecuteOrganisationAction } from '@documenso/lib/utils/organisations'; @@ -19,7 +20,6 @@ import { type TDocumentPreferencesFormSchema, } from '~/components/forms/document-preferences-form'; import { SettingsHeader } from '~/components/general/settings-header'; -import { useCurrentOrganisation } from '~/providers/organisation'; import type { Route } from './+types/org.$orgUrl.settings.preferences'; diff --git a/apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.teams.tsx b/apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.teams.tsx index f0a3fde2c..35199565f 100644 --- a/apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.teams.tsx +++ b/apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.teams.tsx @@ -1,18 +1,14 @@ import { useEffect, useState } from 'react'; import { useLingui } from '@lingui/react/macro'; -import { Trans } from '@lingui/react/macro'; -import { Link, useSearchParams } from 'react-router'; +import { useSearchParams } from 'react-router'; import { useLocation } from 'react-router'; import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value'; -import { trpc } from '@documenso/trpc/react'; import { Input } from '@documenso/ui/primitives/input'; -import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs'; import { TeamCreateDialog } from '~/components/dialogs/team-create-dialog'; import { SettingsHeader } from '~/components/general/settings-header'; -import { OrganisationPendingTeamsTable } from '~/components/tables/organisation-pending-teams-table'; import { OrganisationTeamsTable } from '~/components/tables/organisation-teams-table'; export default function OrganisationSettingsTeamsPage() { @@ -25,15 +21,6 @@ export default function OrganisationSettingsTeamsPage() { const debouncedSearchQuery = useDebouncedValue(searchQuery, 500); - const currentTab = searchParams?.get('tab') === 'pending' ? 'pending' : 'active'; - - const { data } = trpc.team.findTeamsPending.useQuery( - {}, - { - placeholderData: (previousData) => previousData, - }, - ); - /** * Handle debouncing the search query. */ @@ -55,36 +42,14 @@ export default function OrganisationSettingsTeamsPage() { -
-
- setSearchQuery(e.target.value)} - placeholder={t`Search`} - /> + setSearchQuery(e.target.value)} + placeholder={t`Search`} + className="mb-4" + /> - - - - - Active - - - - - - Pending - {data && data.count > 0 && ( - {data.count} - )} - - - - -
- - {currentTab === 'pending' ? : } -
+
); } diff --git a/apps/remix/app/routes/_authenticated+/settings+/billing.tsx b/apps/remix/app/routes/_authenticated+/settings+/billing.tsx deleted file mode 100644 index 893793ca8..000000000 --- a/apps/remix/app/routes/_authenticated+/settings+/billing.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Trans } from '@lingui/react/macro'; - -import { appMetaTags } from '~/utils/meta'; - -export function meta() { - return appMetaTags('Billing'); -} - -export default function TeamsSettingBillingPage() { - return ( -
-
-
-

- Billing -

- -
- Billing has been moved to organisations -
-
-
-
- ); -} diff --git a/apps/remix/app/routes/_authenticated+/settings+/organisations.tsx b/apps/remix/app/routes/_authenticated+/settings+/organisations.tsx index b89cd1b77..4d6765cb9 100644 --- a/apps/remix/app/routes/_authenticated+/settings+/organisations.tsx +++ b/apps/remix/app/routes/_authenticated+/settings+/organisations.tsx @@ -4,7 +4,7 @@ import { useLingui } from '@lingui/react'; import { OrganisationCreateDialog } from '~/components/dialogs/organisation-create-dialog'; import { OrganisationInvitations } from '~/components/general/organisations/organisation-invitations'; import { SettingsHeader } from '~/components/general/settings-header'; -import { UserSettingsOrganisationsTable } from '~/components/tables/user-settings-organisations-table'; +import { UserOrganisationsTable } from '~/components/tables/user-organisations-table'; export default function TeamsSettingsPage() { const { _ } = useLingui(); @@ -18,7 +18,7 @@ export default function TeamsSettingsPage() { - +
diff --git a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id.edit.tsx b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id.edit.tsx index ec02f8323..179ceb4c1 100644 --- a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id.edit.tsx +++ b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id.edit.tsx @@ -5,7 +5,6 @@ import { Link, redirect } from 'react-router'; import { match } from 'ts-pattern'; import { getSession } from '@documenso/auth/server/lib/utils/get-session'; -import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; import { getDocumentWithDetailsById } from '@documenso/lib/server-only/document/get-document-with-details-by-id'; import { getTeamByUrl } from '@documenso/lib/server-only/team/get-team'; import { DocumentVisibility } from '@documenso/lib/types/document-visibility'; @@ -78,20 +77,14 @@ export async function loader({ params, request }: Route.LoaderArgs) { throw redirect(`${documentRootPath}/${documentId}`); } - const isDocumentEnterprise = await isUserEnterprise({ - userId: user.id, - teamId: team?.id, - }); - return superLoaderJson({ document, documentRootPath, - isDocumentEnterprise, }); } export default function DocumentEditPage() { - const { document, documentRootPath, isDocumentEnterprise } = useSuperLoaderData(); + const { document, documentRootPath } = useSuperLoaderData(); const { recipients } = document; @@ -133,7 +126,6 @@ export default function DocumentEditPage() { className="mt-6" initialDocument={document} documentRootPath={documentRootPath} - isDocumentEnterprise={isDocumentEnterprise} />
); diff --git a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.groups.tsx b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.groups.tsx index f7300a30b..c6d1aa6eb 100644 --- a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.groups.tsx +++ b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.groups.tsx @@ -11,6 +11,7 @@ import { import { useLocation, useSearchParams } from 'react-router'; import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value'; +import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; import { trpc } from '@documenso/trpc/react'; import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert'; import { Button } from '@documenso/ui/primitives/button'; @@ -30,7 +31,6 @@ import { useToast } from '@documenso/ui/primitives/use-toast'; import { TeamGroupCreateDialog } from '~/components/dialogs/team-group-create-dialog'; import { SettingsHeader } from '~/components/general/settings-header'; import { TeamGroupsTable } from '~/components/tables/team-groups-table'; -import { useCurrentOrganisation } from '~/providers/organisation'; import { useCurrentTeam } from '~/providers/team'; export default function TeamsSettingsGroupsPage() { diff --git a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.members.tsx b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.members.tsx index f231b0c92..100569623 100644 --- a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.members.tsx +++ b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.members.tsx @@ -1,7 +1,6 @@ import { useEffect, useState } from 'react'; -import { msg } from '@lingui/core/macro'; -import { useLingui } from '@lingui/react'; +import { useLingui } from '@lingui/react/macro'; import { useLocation, useSearchParams } from 'react-router'; import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value'; @@ -9,10 +8,10 @@ import { Input } from '@documenso/ui/primitives/input'; import { TeamMemberCreateDialog } from '~/components/dialogs/team-member-create-dialog'; import { SettingsHeader } from '~/components/general/settings-header'; -import { TeamMembersDataTable } from '~/components/tables/team-members-table'; +import { TeamMembersTable } from '~/components/tables/team-members-table'; export default function TeamsSettingsMembersPage() { - const { _ } = useLingui(); + const { t } = useLingui(); const [searchParams, setSearchParams] = useSearchParams(); const { pathname } = useLocation(); @@ -43,21 +42,18 @@ export default function TeamsSettingsMembersPage() { return (
- + setSearchQuery(e.target.value)} - placeholder={_(msg`Search`)} + placeholder={t`Search`} className="mb-4" /> - +
); } diff --git a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx index 56b7053a0..d87e3da0f 100644 --- a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx +++ b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx @@ -2,6 +2,7 @@ import { Trans, useLingui } from '@lingui/react/macro'; import { Loader } from 'lucide-react'; import { Link } from 'react-router'; +import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; import { DocumentSignatureType } from '@documenso/lib/constants/document'; import { canExecuteOrganisationAction } from '@documenso/lib/utils/organisations'; import { trpc } from '@documenso/trpc/react'; @@ -14,7 +15,6 @@ import { type TDocumentPreferencesFormSchema, } from '~/components/forms/document-preferences-form'; import { SettingsHeader } from '~/components/general/settings-header'; -import { useCurrentOrganisation } from '~/providers/organisation'; import { useCurrentTeam } from '~/providers/team'; export default function TeamsSettingsPage() { diff --git a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/templates.$id.edit.tsx b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/templates.$id.edit.tsx index 029628809..359d9b23f 100644 --- a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/templates.$id.edit.tsx +++ b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/templates.$id.edit.tsx @@ -3,7 +3,6 @@ import { ChevronLeft } from 'lucide-react'; import { Link, redirect } from 'react-router'; import { getSession } from '@documenso/auth/server/lib/utils/get-session'; -import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; import { type TGetTeamByUrlResponse, getTeamByUrl } from '@documenso/lib/server-only/team/get-team'; import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id'; import { formatTemplatesPath } from '@documenso/lib/utils/teams'; @@ -43,20 +42,14 @@ export async function loader({ params, request }: Route.LoaderArgs) { throw redirect(templateRootPath); } - const isTemplateEnterprise = await isUserEnterprise({ - userId: user.id, - teamId: team?.id, - }); - return superLoaderJson({ template, - isTemplateEnterprise, templateRootPath, }); } export default function TemplateEditPage() { - const { template, isTemplateEnterprise, templateRootPath } = useSuperLoaderData(); + const { template, templateRootPath } = useSuperLoaderData(); return (
@@ -99,7 +92,6 @@ export default function TemplateEditPage() { className="mt-6" initialTemplate={template} templateRootPath={templateRootPath} - isEnterprise={isTemplateEnterprise} />
); diff --git a/apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx b/apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx index 025efdada..f49ef7fd6 100644 --- a/apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx +++ b/apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx @@ -6,7 +6,6 @@ import { redirect } from 'react-router'; import { match } from 'ts-pattern'; import { UAParser } from 'ua-parser-js'; -import { isDocumentPlatform } from '@documenso/ee/server-only/util/is-document-platform'; import { APP_I18N_OPTIONS, ZSupportedLanguageCodeSchema } from '@documenso/lib/constants/i18n'; import { RECIPIENT_ROLES_DESCRIPTION, @@ -28,8 +27,6 @@ import { TableRow, } from '@documenso/ui/primitives/table'; -import { BrandingLogo } from '~/components/general/branding-logo'; - import type { Route } from './+types/certificate'; const FRIENDLY_SIGNING_REASONS = { @@ -60,8 +57,6 @@ export async function loader({ request }: Route.LoaderArgs) { throw redirect('/'); } - const isPlatformDocument = await isDocumentPlatform(document); - const documentLanguage = ZSupportedLanguageCodeSchema.parse(document.documentMeta?.language); const auditLogs = await getDocumentCertificateAuditLogs({ @@ -73,7 +68,6 @@ export async function loader({ request }: Route.LoaderArgs) { return { document, documentLanguage, - isPlatformDocument, auditLogs, messages, }; @@ -89,7 +83,7 @@ export async function loader({ request }: Route.LoaderArgs) { * Update: Maybe tags work now after RR7 migration. */ export default function SigningCertificate({ loaderData }: Route.ComponentProps) { - const { document, documentLanguage, isPlatformDocument, auditLogs, messages } = loaderData; + const { document, documentLanguage, auditLogs, messages } = loaderData; const { i18n, _ } = useLingui(); @@ -341,7 +335,8 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps) - {isPlatformDocument && ( + {/* Todo: orgs - This shit does not make sense */} + {/* {isPlatformDocument && (

@@ -351,7 +346,7 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps)

- )} + )} */}
); } diff --git a/apps/remix/app/routes/_unauthenticated+/organisation.invite.$token.tsx b/apps/remix/app/routes/_unauthenticated+/organisation.invite.$token.tsx index 97e07e6b0..5fa253ca3 100644 --- a/apps/remix/app/routes/_unauthenticated+/organisation.invite.$token.tsx +++ b/apps/remix/app/routes/_unauthenticated+/organisation.invite.$token.tsx @@ -1,5 +1,4 @@ import { Trans } from '@lingui/react/macro'; -import { OrganisationMemberInviteStatus } from '@prisma/client'; import { Link } from 'react-router'; import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session'; @@ -53,19 +52,6 @@ export async function loader({ params, request }: Route.LoaderArgs) { await acceptOrganisationInvitation({ token: organisationMemberInvite.token }); } - // For users who do not exist yet, set the team invite status to accepted, which is checked during - // user creation to determine if we should add the user to the team at that time. - if (!user && organisationMemberInvite.status !== OrganisationMemberInviteStatus.ACCEPTED) { - await prisma.organisationMemberInvite.update({ - where: { - id: organisationMemberInvite.id, - }, - data: { - status: OrganisationMemberInviteStatus.ACCEPTED, - }, - }); - } - if (!user) { return { state: 'LoginRequired', diff --git a/apps/remix/app/routes/embed+/direct.$url.tsx b/apps/remix/app/routes/embed+/direct.$url.tsx index 2afb5c2b1..6b3995b3c 100644 --- a/apps/remix/app/routes/embed+/direct.$url.tsx +++ b/apps/remix/app/routes/embed+/direct.$url.tsx @@ -2,10 +2,8 @@ import { data } from 'react-router'; import { match } from 'ts-pattern'; import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session'; -import { isCommunityPlan as isUserCommunityPlan } from '@documenso/ee/server-only/util/is-community-plan'; -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 { getOrganisationClaimByTeamId } from '@documenso/lib/server-only/organisation/get-organisation-claims'; import { getTeamSettings } from '@documenso/lib/server-only/team/get-team-settings'; import { getTemplateByDirectLinkToken } from '@documenso/lib/server-only/template/get-template-by-direct-link-token'; import { DocumentAccessAuth } from '@documenso/lib/types/document-auth'; @@ -36,10 +34,15 @@ export async function loader({ params, request }: Route.LoaderArgs) { throw new Response('Not found', { status: 404 }); } + const organisationClaim = await getOrganisationClaimByTeamId({ teamId: template.teamId }); + + const allowCustomBranding = organisationClaim.flags.branding; + const allowEmbedSigningWhitelabel = organisationClaim.flags.embedSigningWhiteLabel; + // TODO: Make this more robust, we need to ensure the owner is either // TODO: the member of a team that has an active subscription, is an early // TODO: adopter or is an enterprise user. - if (IS_BILLING_ENABLED() && !template.teamId) { + if (IS_BILLING_ENABLED() && !organisationClaim.flags.embedSigning) { throw data( { type: 'embed-paywall', @@ -56,18 +59,6 @@ export async function loader({ params, request }: Route.LoaderArgs) { documentAuth: template.authOptions, }); - const [isPlatformDocument, isEnterpriseDocument, isCommunityPlan] = await Promise.all([ - isDocumentPlatform(template), - isUserEnterprise({ - userId: template.userId, - teamId: template.teamId ?? undefined, - }), - isUserCommunityPlan({ - userId: template.userId, - teamId: template.teamId ?? undefined, - }), - ]); - const isAccessAuthValid = match(derivedRecipientAccessAuth) .with(DocumentAccessAuth.ACCOUNT, () => user !== null) .with(null, () => true) @@ -102,7 +93,8 @@ export async function loader({ params, request }: Route.LoaderArgs) { teamId: template.teamId, }); - const hidePoweredBy = settings.brandingHidePoweredBy; + // Todo: orgs - figure out + // const hidePoweredBy = settings.brandingHidePoweredBy; return superLoaderJson({ token, @@ -110,10 +102,8 @@ export async function loader({ params, request }: Route.LoaderArgs) { template, recipient, fields, - hidePoweredBy, - isPlatformDocument, - isEnterpriseDocument, - isCommunityPlan, + allowCustomBranding, + allowEmbedSigningWhitelabel, }); } @@ -124,10 +114,8 @@ export default function EmbedDirectTemplatePage() { template, recipient, fields, - hidePoweredBy, - isPlatformDocument, - isEnterpriseDocument, - isCommunityPlan, + allowCustomBranding, + allowEmbedSigningWhitelabel, } = useSuperLoaderData(); return ( @@ -152,10 +140,8 @@ export default function EmbedDirectTemplatePage() { recipient={recipient} fields={fields} metadata={template.templateMeta} - hidePoweredBy={ - isCommunityPlan || isPlatformDocument || isEnterpriseDocument || hidePoweredBy - } - allowWhiteLabelling={isCommunityPlan || isPlatformDocument || isEnterpriseDocument} + hidePoweredBy={allowCustomBranding} + allowWhiteLabelling={allowEmbedSigningWhitelabel} /> diff --git a/apps/remix/app/routes/embed+/sign.$url.tsx b/apps/remix/app/routes/embed+/sign.$url.tsx index 5eecf861a..22f7c14cb 100644 --- a/apps/remix/app/routes/embed+/sign.$url.tsx +++ b/apps/remix/app/routes/embed+/sign.$url.tsx @@ -3,13 +3,11 @@ import { data } from 'react-router'; import { match } from 'ts-pattern'; import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session'; -import { isCommunityPlan as isUserCommunityPlan } from '@documenso/ee/server-only/util/is-community-plan'; -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 { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token'; import { getCompletedFieldsForToken } from '@documenso/lib/server-only/field/get-completed-fields-for-token'; import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token'; +import { getOrganisationClaimByTeamId } from '@documenso/lib/server-only/organisation/get-organisation-claims'; import { getIsRecipientsTurnToSign } from '@documenso/lib/server-only/recipient/get-is-recipient-turn'; import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token'; import { getRecipientsForAssistant } from '@documenso/lib/server-only/recipient/get-recipients-for-assistant'; @@ -51,10 +49,16 @@ export async function loader({ params, request }: Route.LoaderArgs) { throw new Response('Not found', { status: 404 }); } + // Todo: orgs - test + const organisationClaim = await getOrganisationClaimByTeamId({ teamId: document.teamId }); + + const allowEmbedSigningWhitelabel = organisationClaim.flags.embedSigningWhiteLabel; + const allowCustomBranding = organisationClaim.flags.branding; + // TODO: Make this more robust, we need to ensure the owner is either // TODO: the member of a team that has an active subscription, is an early // TODO: adopter or is an enterprise user. - if (IS_BILLING_ENABLED() && !document.teamId) { + if (IS_BILLING_ENABLED() && !organisationClaim.flags.embedSigning) { throw data( { type: 'embed-paywall', @@ -65,18 +69,6 @@ export async function loader({ params, request }: Route.LoaderArgs) { ); } - const [isPlatformDocument, isEnterpriseDocument, isCommunityPlan] = await Promise.all([ - isDocumentPlatform(document), - isUserEnterprise({ - userId: document.userId, - teamId: document.teamId ?? undefined, - }), - isUserCommunityPlan({ - userId: document.userId, - teamId: document.teamId ?? undefined, - }), - ]); - const { derivedRecipientAccessAuth } = extractDocumentAuthMethods({ documentAuth: document.authOptions, }); @@ -123,7 +115,8 @@ export async function loader({ params, request }: Route.LoaderArgs) { teamId: document.teamId, }); - const hidePoweredBy = settings.brandingHidePoweredBy; + // Todo: orgs - figure out + // const hidePoweredBy = settings.brandingHidePoweredBy; return superLoaderJson({ token, @@ -133,10 +126,8 @@ export async function loader({ params, request }: Route.LoaderArgs) { recipient, fields, completedFields, - hidePoweredBy, - isPlatformDocument, - isEnterpriseDocument, - isCommunityPlan, + allowCustomBranding, + allowEmbedSigningWhitelabel, }); } @@ -149,10 +140,8 @@ export default function EmbedSignDocumentPage() { recipient, fields, completedFields, - hidePoweredBy, - isPlatformDocument, - isEnterpriseDocument, - isCommunityPlan, + allowCustomBranding, + allowEmbedSigningWhitelabel, } = useSuperLoaderData(); return ( @@ -178,10 +167,8 @@ export default function EmbedSignDocumentPage() { completedFields={completedFields} metadata={document.documentMeta} isCompleted={isDocumentCompleted(document.status)} - hidePoweredBy={ - isCommunityPlan || isPlatformDocument || isEnterpriseDocument || hidePoweredBy - } - allowWhitelabelling={isCommunityPlan || isPlatformDocument || isEnterpriseDocument} + hidePoweredBy={allowCustomBranding} + allowWhitelabelling={allowEmbedSigningWhitelabel} allRecipients={allRecipients} /> diff --git a/packages/api/v1/implementation.ts b/packages/api/v1/implementation.ts index 6b0236c7b..2de679019 100644 --- a/packages/api/v1/implementation.ts +++ b/packages/api/v1/implementation.ts @@ -255,7 +255,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, { }; } - const { remaining } = await getServerLimits({ email: user.email, teamId: team?.id }); + const { remaining } = await getServerLimits({ teamId: team.id }); if (remaining.documents <= 0) { return { @@ -464,7 +464,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, { createDocumentFromTemplate: authenticatedMiddleware(async (args, user, team, { metadata }) => { const { body, params } = args; - const { remaining } = await getServerLimits({ email: user.email, teamId: team?.id }); + const { remaining } = await getServerLimits({ teamId: team?.id }); if (remaining.documents <= 0) { return { @@ -562,7 +562,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, { generateDocumentFromTemplate: authenticatedMiddleware(async (args, user, team, { metadata }) => { const { body, params } = args; - const { remaining } = await getServerLimits({ email: user.email, teamId: team?.id }); + const { remaining } = await getServerLimits({ teamId: team?.id }); if (remaining.documents <= 0) { return { diff --git a/packages/app-tests/e2e/teams/team-members.spec.ts b/packages/app-tests/e2e/teams/team-members.spec.ts index 3bf9387d4..58e6dbdb5 100644 --- a/packages/app-tests/e2e/teams/team-members.spec.ts +++ b/packages/app-tests/e2e/teams/team-members.spec.ts @@ -48,7 +48,7 @@ test('[TEAMS]: accept team invitation without account', async ({ page }) => { teamId: team.id, }); - await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/team/invite/${teamInvite.token}`); + await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/organisation/invite/${teamInvite.token}`); await expect(page.getByRole('heading')).toContainText('Team invitation'); }); @@ -61,7 +61,7 @@ test('[TEAMS]: accept team invitation with account', async ({ page }) => { teamId: team.id, }); - await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/team/invite/${teamInvite.token}`); + await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/organisation/invite/${teamInvite.token}`); await expect(page.getByRole('heading')).toContainText('Invitation accepted!'); }); diff --git a/packages/auth/server/lib/session/session.ts b/packages/auth/server/lib/session/session.ts index ff072e6ad..15e1cc04b 100644 --- a/packages/auth/server/lib/session/session.ts +++ b/packages/auth/server/lib/session/session.ts @@ -22,7 +22,6 @@ export type SessionUser = Pick< | 'twoFactorEnabled' | 'roles' | 'signature' - | 'customerId' >; export type SessionValidationResult = @@ -98,7 +97,6 @@ export const validateSessionToken = async (token: string): Promise() }); } - const { name, email, password, signature, url } = c.req.valid('json'); + const { name, email, password, signature } = c.req.valid('json'); - if (IS_BILLING_ENABLED() && url && url.length < 6) { - throw new AppError('PREMIUM_PROFILE_URL', { - message: 'Only subscribers can have a username shorter than 6 characters', - }); - } - - const orgUrl = url || alphaid(12); - - const user = await createUser({ name, email, password, signature, orgUrl }).catch((err) => { + const user = await createUser({ name, email, password, signature }).catch((err) => { console.error(err); throw err; }); diff --git a/packages/auth/server/types/email-password.ts b/packages/auth/server/types/email-password.ts index aeaae5a02..e8d2edcdd 100644 --- a/packages/auth/server/types/email-password.ts +++ b/packages/auth/server/types/email-password.ts @@ -37,15 +37,6 @@ export const ZSignUpSchema = z.object({ email: z.string().email(), password: ZPasswordSchema, signature: z.string().nullish(), - url: z - .string() - .trim() - .toLowerCase() - .min(1) - .regex(/^[a-z0-9-]+$/, { - message: 'Username can only container alphanumeric characters and dashes.', - }) - .optional(), }); export type TSignUpSchema = z.infer; diff --git a/packages/ee/server-only/limits/constants.ts b/packages/ee/server-only/limits/constants.ts index b3ca4c81b..436645685 100644 --- a/packages/ee/server-only/limits/constants.ts +++ b/packages/ee/server-only/limits/constants.ts @@ -6,7 +6,13 @@ export const FREE_PLAN_LIMITS: TLimitsSchema = { directTemplates: 3, }; -export const TEAM_PLAN_LIMITS: TLimitsSchema = { +export const INACTIVE_PLAN_LIMITS: TLimitsSchema = { + documents: 0, + recipients: 0, + directTemplates: 0, +}; + +export const PAID_PLAN_LIMITS: TLimitsSchema = { documents: Infinity, recipients: Infinity, directTemplates: Infinity, diff --git a/packages/ee/server-only/limits/handler.ts b/packages/ee/server-only/limits/handler.ts index e75259ced..2f10a5044 100644 --- a/packages/ee/server-only/limits/handler.ts +++ b/packages/ee/server-only/limits/handler.ts @@ -17,11 +17,11 @@ export const limitsHandler = async (req: Request) => { teamId = parseInt(rawTeamId, 10); } - if (!teamId && rawTeamId) { + if (!teamId) { throw new Error(ERROR_CODES.INVALID_TEAM_ID); } - const limits = await getServerLimits({ email: user.email, teamId }); + const limits = await getServerLimits({ userId: user.id, teamId }); return Response.json(limits, { status: 200, diff --git a/packages/ee/server-only/limits/server.ts b/packages/ee/server-only/limits/server.ts index 59ee9006a..6242ddd8f 100644 --- a/packages/ee/server-only/limits/server.ts +++ b/packages/ee/server-only/limits/server.ts @@ -2,21 +2,25 @@ import { DocumentSource, SubscriptionStatus } from '@prisma/client'; import { DateTime } from 'luxon'; import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app'; +import { INTERNAL_CLAIM_ID } from '@documenso/lib/types/subscription'; import { prisma } from '@documenso/prisma'; -import { getDocumentRelatedPrices } from '../stripe/get-document-related-prices.ts'; -import { FREE_PLAN_LIMITS, SELFHOSTED_PLAN_LIMITS, TEAM_PLAN_LIMITS } from './constants'; +import { + FREE_PLAN_LIMITS, + INACTIVE_PLAN_LIMITS, + PAID_PLAN_LIMITS, + SELFHOSTED_PLAN_LIMITS, +} from './constants'; import { ERROR_CODES } from './errors'; import type { TLimitsResponseSchema } from './schema'; -import { ZLimitsSchema } from './schema'; export type GetServerLimitsOptions = { - email: string; - teamId: number | null; + userId: number; + teamId: number; }; export const getServerLimits = async ({ - email, + userId, teamId, }: GetServerLimitsOptions): Promise => { if (!IS_BILLING_ENABLED()) { @@ -26,141 +30,89 @@ export const getServerLimits = async ({ }; } - if (!email) { - throw new Error(ERROR_CODES.UNAUTHORIZED); - } - - return teamId ? handleTeamLimits({ email, teamId }) : handleUserLimits({ email }); -}; - -type HandleUserLimitsOptions = { - email: string; -}; - -const handleUserLimits = async ({ email }: HandleUserLimitsOptions) => { - const user = await prisma.user.findFirst({ + const organisation = await prisma.organisation.findFirst({ where: { - email, - }, - include: { - subscriptions: true, - }, - }); - - if (!user) { - throw new Error(ERROR_CODES.USER_FETCH_FAILED); - } - - let quota = structuredClone(FREE_PLAN_LIMITS); - let remaining = structuredClone(FREE_PLAN_LIMITS); - - const activeSubscriptions = user.subscriptions.filter( - ({ status }) => status === SubscriptionStatus.ACTIVE, - ); - - if (activeSubscriptions.length > 0) { - const documentPlanPrices = await getDocumentRelatedPrices(); - - for (const subscription of activeSubscriptions) { - const price = documentPlanPrices.find((price) => price.id === subscription.priceId); - - if (!price || typeof price.product === 'string' || price.product.deleted) { - continue; - } - - const currentQuota = ZLimitsSchema.parse( - 'metadata' in price.product ? price.product.metadata : {}, - ); - - // Use the subscription with the highest quota. - if (currentQuota.documents > quota.documents && currentQuota.recipients > quota.recipients) { - quota = currentQuota; - remaining = structuredClone(quota); - } - } - - // Assume all active subscriptions provide unlimited direct templates. - remaining.directTemplates = Infinity; - } - - const [documents, directTemplates] = await Promise.all([ - prisma.document.count({ - where: { - userId: user.id, - teamId: null, - createdAt: { - gte: DateTime.utc().startOf('month').toJSDate(), - }, - source: { - not: DocumentSource.TEMPLATE_DIRECT_LINK, + teams: { + some: { + id: teamId, }, }, - }), - prisma.template.count({ - where: { - userId: user.id, - teamId: null, - directLink: { - isNot: null, - }, - }, - }), - ]); - - remaining.documents = Math.max(remaining.documents - documents, 0); - remaining.directTemplates = Math.max(remaining.directTemplates - directTemplates, 0); - - return { - quota, - remaining, - }; -}; - -type HandleTeamLimitsOptions = { - email: string; - teamId: number; -}; - -const handleTeamLimits = async ({ email, teamId }: HandleTeamLimitsOptions) => { - const team = await prisma.team.findFirst({ - where: { - id: teamId, members: { some: { - user: { - email, - }, + userId, }, }, }, include: { subscription: true, + organisationClaim: true, }, }); - if (!team) { - throw new Error('Team not found'); + if (!organisation) { + throw new Error(ERROR_CODES.USER_FETCH_FAILED); } - const { subscription } = team; + const quota = structuredClone(FREE_PLAN_LIMITS); + const remaining = structuredClone(FREE_PLAN_LIMITS); - if (subscription && subscription.status === SubscriptionStatus.INACTIVE) { + const subscription = organisation.subscription; + + // Bypass all limits even if plan expired for ENTERPRISE. + if (organisation.organisationClaimId === INTERNAL_CLAIM_ID.ENTERPRISE) { return { - quota: { - documents: 0, - recipients: 0, - directTemplates: 0, - }, - remaining: { - documents: 0, - recipients: 0, - directTemplates: 0, - }, + quota: PAID_PLAN_LIMITS, + remaining: PAID_PLAN_LIMITS, + }; + } + + // If free tier or plan does not have unlimited documents. + if (!subscription || !organisation.organisationClaim.flags.unlimitedDocuments) { + const [documents, directTemplates] = await Promise.all([ + prisma.document.count({ + where: { + team: { + organisationId: organisation.id, + }, + createdAt: { + gte: DateTime.utc().startOf('month').toJSDate(), + }, + source: { + not: DocumentSource.TEMPLATE_DIRECT_LINK, + }, + }, + }), + prisma.template.count({ + where: { + team: { + organisationId: organisation.id, + }, + directLink: { + isNot: null, + }, + }, + }), + ]); + + remaining.documents = Math.max(remaining.documents - documents, 0); + remaining.directTemplates = Math.max(remaining.directTemplates - directTemplates, 0); + + return { + quota, + remaining, + }; + } + + // If plan expired. + if (subscription.status !== SubscriptionStatus.ACTIVE) { + return { + quota: INACTIVE_PLAN_LIMITS, + remaining: INACTIVE_PLAN_LIMITS, }; } return { - quota: structuredClone(TEAM_PLAN_LIMITS), - remaining: structuredClone(TEAM_PLAN_LIMITS), + quota: PAID_PLAN_LIMITS, + remaining: PAID_PLAN_LIMITS, }; }; diff --git a/packages/ee/server-only/stripe/get-checkout-session.ts b/packages/ee/server-only/stripe/create-checkout-session.ts similarity index 61% rename from packages/ee/server-only/stripe/get-checkout-session.ts rename to packages/ee/server-only/stripe/create-checkout-session.ts index 5633b642d..466b0132e 100644 --- a/packages/ee/server-only/stripe/get-checkout-session.ts +++ b/packages/ee/server-only/stripe/create-checkout-session.ts @@ -1,20 +1,23 @@ import type Stripe from 'stripe'; +import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import { stripe } from '@documenso/lib/server-only/stripe'; -export type GetCheckoutSessionOptions = { +export type CreateCheckoutSessionOptions = { customerId: string; priceId: string; returnUrl: string; subscriptionMetadata?: Stripe.Metadata; }; -export const getCheckoutSession = async ({ +// Todo: orgs validate priceId to ensure it's only ones we allow + +export const createCheckoutSession = async ({ customerId, priceId, returnUrl, subscriptionMetadata, -}: GetCheckoutSessionOptions) => { +}: CreateCheckoutSessionOptions) => { const session = await stripe.checkout.sessions.create({ customer: customerId, mode: 'subscription', @@ -31,5 +34,11 @@ export const getCheckoutSession = async ({ }, }); + if (!session.url) { + throw new AppError(AppErrorCode.UNKNOWN_ERROR, { + message: 'Failed to create checkout session', + }); + } + return session.url; }; diff --git a/packages/ee/server-only/stripe/create-customer.ts b/packages/ee/server-only/stripe/create-customer.ts new file mode 100644 index 000000000..713796793 --- /dev/null +++ b/packages/ee/server-only/stripe/create-customer.ts @@ -0,0 +1,13 @@ +import { stripe } from '@documenso/lib/server-only/stripe'; + +type CreateCustomerOptions = { + name: string; + email: string; +}; + +export const createCustomer = async ({ name, email }: CreateCustomerOptions) => { + return await stripe.customers.create({ + name, + email, + }); +}; diff --git a/packages/ee/server-only/stripe/create-team-customer.ts b/packages/ee/server-only/stripe/create-team-customer.ts deleted file mode 100644 index 3285b8c26..000000000 --- a/packages/ee/server-only/stripe/create-team-customer.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { STRIPE_CUSTOMER_TYPE } from '@documenso/lib/constants/billing'; -import { stripe } from '@documenso/lib/server-only/stripe'; - -type CreateOrganisationCustomerOptions = { - name: string; - email: string; -}; - -/** - * Create a Stripe customer for a given Organisation. - */ -export const createOrganisationCustomer = async ({ - name, - email, -}: CreateOrganisationCustomerOptions) => { - return await stripe.customers.create({ - name, - email, - metadata: { - type: STRIPE_CUSTOMER_TYPE.ORGANISATION, - }, - }); -}; diff --git a/packages/ee/server-only/stripe/delete-customer-payment-methods.ts b/packages/ee/server-only/stripe/delete-customer-payment-methods.ts deleted file mode 100644 index 749c15763..000000000 --- a/packages/ee/server-only/stripe/delete-customer-payment-methods.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { stripe } from '@documenso/lib/server-only/stripe'; - -type DeleteCustomerPaymentMethodsOptions = { - customerId: string; -}; - -/** - * Delete all attached payment methods for a given customer. - */ -export const deleteCustomerPaymentMethods = async ({ - customerId, -}: DeleteCustomerPaymentMethodsOptions) => { - const paymentMethods = await stripe.paymentMethods.list({ - customer: customerId, - }); - - await Promise.all( - paymentMethods.data.map(async (paymentMethod) => - stripe.paymentMethods.detach(paymentMethod.id), - ), - ); -}; diff --git a/packages/ee/server-only/stripe/get-community-plan-prices.ts b/packages/ee/server-only/stripe/get-community-plan-prices.ts deleted file mode 100644 index 86c7f61bd..000000000 --- a/packages/ee/server-only/stripe/get-community-plan-prices.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { STRIPE_PLAN_TYPE } from '@documenso/lib/constants/billing'; - -import { getPricesByPlan } from './get-prices-by-plan'; - -export const getCommunityPlanPrices = async () => { - return await getPricesByPlan(STRIPE_PLAN_TYPE.COMMUNITY); -}; - -export const getCommunityPlanPriceIds = async () => { - const prices = await getCommunityPlanPrices(); - - return prices.map((price) => price.id); -}; diff --git a/packages/ee/server-only/stripe/get-customer.ts b/packages/ee/server-only/stripe/get-customer.ts index cb2984f6a..30739fe8c 100644 --- a/packages/ee/server-only/stripe/get-customer.ts +++ b/packages/ee/server-only/stripe/get-customer.ts @@ -1,21 +1,4 @@ -import type { User } from '@prisma/client'; - -import { STRIPE_CUSTOMER_TYPE } from '@documenso/lib/constants/billing'; import { stripe } from '@documenso/lib/server-only/stripe'; -import { prisma } from '@documenso/prisma'; - -import { onSubscriptionUpdated } from './webhook/on-subscription-updated'; - -/** - * Get a non team Stripe customer by email. - */ -export const getStripeCustomerByEmail = async (email: string) => { - const foundStripeCustomers = await stripe.customers.list({ - email, - }); - - return foundStripeCustomers.data.find((customer) => customer.metadata.type !== 'team') ?? null; -}; export const getStripeCustomerById = async (stripeCustomerId: string) => { try { @@ -26,86 +9,3 @@ export const getStripeCustomerById = async (stripeCustomerId: string) => { return null; } }; - -// Todo: (orgs) -/** - * Get a stripe customer by user. - * - * Will create a Stripe customer and update the relevant user if one does not exist. - */ -export const getStripeCustomerByUser = async ( - user: Pick, -) => { - if (user.customerId) { - const stripeCustomer = await getStripeCustomerById(user.customerId); - - if (!stripeCustomer) { - throw new Error('Missing Stripe customer'); - } - - return { - user, - stripeCustomer, - }; - } - - let stripeCustomer = await getStripeCustomerByEmail(user.email); - - const isSyncRequired = Boolean(stripeCustomer && !stripeCustomer.deleted); - - if (!stripeCustomer) { - stripeCustomer = await stripe.customers.create({ - name: user.name ?? undefined, - email: user.email, - metadata: { - userId: user.id, - type: STRIPE_CUSTOMER_TYPE.INDIVIDUAL, - }, - }); - } - - const updatedUser = await prisma.user.update({ - where: { - id: user.id, - }, - data: { - customerId: stripeCustomer.id, - }, - }); - - // Sync subscriptions if the customer already exists for back filling the DB - // and local development. - if (isSyncRequired) { - await syncStripeCustomerSubscriptions(user.id, stripeCustomer.id).catch((e) => { - console.error(e); - }); - } - - return { - user: updatedUser, - stripeCustomer, - }; -}; - -export const getStripeCustomerIdByUser = async (user: User) => { - if (user.customerId !== null) { - return user.customerId; - } - - return await getStripeCustomerByUser(user).then((session) => session.stripeCustomer.id); -}; - -const syncStripeCustomerSubscriptions = async (userId: number, stripeCustomerId: string) => { - const stripeSubscriptions = await stripe.subscriptions.list({ - customer: stripeCustomerId, - }); - - await Promise.all( - stripeSubscriptions.data.map(async (subscription) => - onSubscriptionUpdated({ - userId, - subscription, - }), - ), - ); -}; 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 deleted file mode 100644 index 682086734..000000000 --- a/packages/ee/server-only/stripe/get-document-related-prices.ts.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { STRIPE_PLAN_TYPE } from '@documenso/lib/constants/billing'; - -import { getPricesByPlan } from './get-prices-by-plan'; - -/** - * Returns the Stripe prices of items that affect the amount of documents a user can create. - */ -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-enterprise-plan-prices.ts b/packages/ee/server-only/stripe/get-enterprise-plan-prices.ts deleted file mode 100644 index ec67fe163..000000000 --- a/packages/ee/server-only/stripe/get-enterprise-plan-prices.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { STRIPE_PLAN_TYPE } from '@documenso/lib/constants/billing'; - -import { getPricesByPlan } from './get-prices-by-plan'; - -export const getEnterprisePlanPrices = async () => { - return await getPricesByPlan(STRIPE_PLAN_TYPE.ENTERPRISE); -}; - -export const getEnterprisePlanPriceIds = async () => { - const prices = await getEnterprisePlanPrices(); - - return prices.map((price) => price.id); -}; diff --git a/packages/ee/server-only/stripe/get-internal-claim-plans.ts b/packages/ee/server-only/stripe/get-internal-claim-plans.ts new file mode 100644 index 000000000..cf9d3923f --- /dev/null +++ b/packages/ee/server-only/stripe/get-internal-claim-plans.ts @@ -0,0 +1,88 @@ +import { clone } from 'remeda'; +import type Stripe from 'stripe'; + +import { stripe } from '@documenso/lib/server-only/stripe'; +import { + INTERNAL_CLAIM_ID, + type InternalClaim, + internalClaims, +} from '@documenso/lib/types/subscription'; +import { toHumanPrice } from '@documenso/lib/universal/stripe/to-human-price'; + +export type InternalClaimPlans = { + [key in INTERNAL_CLAIM_ID]: InternalClaim & { + monthlyPrice?: Stripe.Price & { + product: Stripe.Product; + isVisibleInApp: boolean; + friendlyPrice: string; + }; + yearlyPrice?: Stripe.Price & { + product: Stripe.Product; + isVisibleInApp: boolean; + friendlyPrice: string; + }; + }; +}; + +/** + * Returns the main Documenso plans from Stripe. + */ +export const getInternalClaimPlans = async (): Promise => { + const { data: prices } = await stripe.prices.search({ + query: `active:'true' type:'recurring'`, + expand: ['data.product'], + limit: 100, + }); + + const plans: InternalClaimPlans = clone(internalClaims); + + prices.forEach((price) => { + // We use `expand` to get the product, but it's not typed as part of the Price type. + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const product = price.product as Stripe.Product; + + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const productClaimId = product.metadata.claimId as INTERNAL_CLAIM_ID | undefined; + const isVisibleInApp = price.metadata.visibleInApp === 'true'; + + if (!productClaimId || !Object.values(INTERNAL_CLAIM_ID).includes(productClaimId)) { + return; + } + + if (product.name.includes('Team')) { + console.log(JSON.stringify(price, null, 2)); + } + + let usdPrice = toHumanPrice(price.unit_amount ?? 0); + + if (price.recurring?.interval === 'month') { + if (product.metadata['isSeatBased'] === 'true') { + usdPrice = '50'; + } + + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + plans[productClaimId].monthlyPrice = { + ...price, + isVisibleInApp, + product, + friendlyPrice: `$${usdPrice} ${price.currency.toUpperCase()}`.replace('.00', ''), + }; + } + + if (price.recurring?.interval === 'year') { + if (product.metadata['isSeatBased'] === 'true') { + usdPrice = '480'; + } + + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + plans[productClaimId].yearlyPrice = { + ...price, + isVisibleInApp, + product, + friendlyPrice: `$${usdPrice} ${price.currency.toUpperCase()}`.replace('.00', ''), + }; + } + }); + + return plans; +}; diff --git a/packages/ee/server-only/stripe/get-platform-plan-prices.ts b/packages/ee/server-only/stripe/get-platform-plan-prices.ts deleted file mode 100644 index 7a55caa07..000000000 --- a/packages/ee/server-only/stripe/get-platform-plan-prices.ts +++ /dev/null @@ -1,13 +0,0 @@ -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-prices-by-interval.ts b/packages/ee/server-only/stripe/get-prices-by-interval.ts deleted file mode 100644 index 41bf88248..000000000 --- a/packages/ee/server-only/stripe/get-prices-by-interval.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type Stripe from 'stripe'; - -import type { STRIPE_PLAN_TYPE } from '@documenso/lib/constants/billing'; -import { stripe } from '@documenso/lib/server-only/stripe'; - -// Utility type to handle usage of the `expand` option. -type PriceWithProduct = Stripe.Price & { product: Stripe.Product }; - -export type PriceIntervals = Record; - -export type GetPricesByIntervalOptions = { - /** - * Filter products by their meta 'plan' attribute. - */ - plans?: STRIPE_PLAN_TYPE[]; -}; - -export const getPricesByInterval = async ({ plans }: GetPricesByIntervalOptions = {}) => { - let { data: prices } = await stripe.prices.search({ - query: `active:'true' type:'recurring'`, - expand: ['data.product'], - limit: 100, - }); - - prices = prices.filter((price) => { - // We use `expand` to get the product, but it's not typed as part of the Price type. - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const product = price.product as Stripe.Product; - - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const filter = !plans || plans.includes(product.metadata?.plan as STRIPE_PLAN_TYPE); - - // Filter out prices for products that are not active. - return product.active && filter; - }); - - const intervals: PriceIntervals = { - day: [], - week: [], - month: [], - year: [], - }; - - // Add each price to the correct interval. - for (const price of prices) { - if (price.recurring?.interval) { - // We use `expand` to get the product, but it's not typed as part of the Price type. - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - intervals[price.recurring.interval].push(price as PriceWithProduct); - } - } - - // Order all prices by unit_amount. - intervals.day.sort((a, b) => Number(a.unit_amount) - Number(b.unit_amount)); - intervals.week.sort((a, b) => Number(a.unit_amount) - Number(b.unit_amount)); - intervals.month.sort((a, b) => Number(a.unit_amount) - Number(b.unit_amount)); - intervals.year.sort((a, b) => Number(a.unit_amount) - Number(b.unit_amount)); - - return intervals; -}; diff --git a/packages/ee/server-only/stripe/get-prices-by-plan.ts b/packages/ee/server-only/stripe/get-prices-by-plan.ts deleted file mode 100644 index 52c9d1e5e..000000000 --- a/packages/ee/server-only/stripe/get-prices-by-plan.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { STRIPE_PLAN_TYPE } from '@documenso/lib/constants/billing'; -import { stripe } from '@documenso/lib/server-only/stripe'; - -type PlanType = (typeof STRIPE_PLAN_TYPE)[keyof typeof STRIPE_PLAN_TYPE]; - -export const getPricesByPlan = async (plan: PlanType | PlanType[]) => { - const planTypes: string[] = typeof plan === 'string' ? [plan] : plan; - - const prices = await stripe.prices.list({ - expand: ['data.product'], - limit: 100, - }); - - return prices.data.filter( - (price) => price.type === 'recurring' && planTypes.includes(price.metadata.plan), - ); -}; 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 deleted file mode 100644 index 166f5c8ef..000000000 --- a/packages/ee/server-only/stripe/get-primary-account-plan-prices.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { STRIPE_PLAN_TYPE } from '@documenso/lib/constants/billing'; - -import { getPricesByPlan } from './get-prices-by-plan'; - -/** - * Returns the prices of items that count as the account's primary plan. - */ -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-product-by-price-id.ts b/packages/ee/server-only/stripe/get-product-by-price-id.ts deleted file mode 100644 index 621c918f2..000000000 --- a/packages/ee/server-only/stripe/get-product-by-price-id.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { stripe } from '@documenso/lib/server-only/stripe'; - -export type GetProductByPriceIdOptions = { - priceId: string; -}; - -export const getProductByPriceId = async ({ priceId }: GetProductByPriceIdOptions) => { - const { product } = await stripe.prices.retrieve(priceId, { - expand: ['product'], - }); - - if (typeof product === 'string' || 'deleted' in product) { - throw new Error('Product not found'); - } - - return product; -}; diff --git a/packages/ee/server-only/stripe/get-subscription.ts b/packages/ee/server-only/stripe/get-subscription.ts new file mode 100644 index 000000000..d9afaa7c8 --- /dev/null +++ b/packages/ee/server-only/stripe/get-subscription.ts @@ -0,0 +1,42 @@ +import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations'; +import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; +import { stripe } from '@documenso/lib/server-only/stripe'; +import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations'; +import { prisma } from '@documenso/prisma'; + +export type GetSubscriptionOptions = { + userId: number; + organisationId: string; +}; + +export const getSubscription = async ({ organisationId, userId }: GetSubscriptionOptions) => { + const organisation = await prisma.organisation.findFirst({ + where: buildOrganisationWhereQuery( + organisationId, + userId, + ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'], + ), + include: { + subscription: true, + }, + }); + + if (!organisation) { + throw new AppError(AppErrorCode.NOT_FOUND, { + message: 'Organisation not found', + }); + } + + if (!organisation.subscription) { + return null; + } + + const stripeSubscription = await stripe.subscriptions.retrieve(organisation.subscription.planId, { + expand: ['items.data.price.product'], + }); + + return { + organisationSubscription: organisation.subscription, + stripeSubscription, + }; +}; diff --git a/packages/ee/server-only/stripe/get-team-prices.ts b/packages/ee/server-only/stripe/get-team-prices.ts deleted file mode 100644 index 03fc65b5f..000000000 --- a/packages/ee/server-only/stripe/get-team-prices.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type Stripe from 'stripe'; - -import { STRIPE_PLAN_TYPE } from '@documenso/lib/constants/billing'; -import { AppError } from '@documenso/lib/errors/app-error'; - -import { getPricesByPlan } from './get-prices-by-plan'; - -export const getTeamPrices = async () => { - const prices = (await getPricesByPlan(STRIPE_PLAN_TYPE.TEAM)).filter((price) => price.active); - - const monthlyPrice = prices.find((price) => price.recurring?.interval === 'month'); - const yearlyPrice = prices.find((price) => price.recurring?.interval === 'year'); - const priceIds = prices.map((price) => price.id); - - if (!monthlyPrice || !yearlyPrice) { - throw new AppError('INVALID_CONFIG', { - message: 'Missing monthly or yearly price', - }); - } - - return { - monthly: { - friendlyInterval: 'Monthly', - interval: 'monthly', - ...extractPriceData(monthlyPrice), - }, - yearly: { - friendlyInterval: 'Yearly', - interval: 'yearly', - ...extractPriceData(yearlyPrice), - }, - priceIds, - } as const; -}; - -const extractPriceData = (price: Stripe.Price) => { - const product = - typeof price.product !== 'string' && !price.product.deleted ? price.product : null; - - return { - priceId: price.id, - description: product?.description ?? '', - features: product?.features ?? [], - }; -}; diff --git a/packages/ee/server-only/stripe/get-team-related-prices.ts b/packages/ee/server-only/stripe/get-team-related-prices.ts deleted file mode 100644 index debbac6ea..000000000 --- a/packages/ee/server-only/stripe/get-team-related-prices.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { STRIPE_PLAN_TYPE } from '@documenso/lib/constants/billing'; - -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.PLATFORM, - STRIPE_PLAN_TYPE.ENTERPRISE, - ]); -}; - -/** - * Returns the Stripe price IDs of items that affect the amount of teams a user can create. - */ -export const getTeamRelatedPriceIds = async () => { - return await getTeamRelatedPrices().then((prices) => prices.map((price) => price.id)); -}; diff --git a/packages/ee/server-only/stripe/is-price-seats-based.ts b/packages/ee/server-only/stripe/is-price-seats-based.ts new file mode 100644 index 000000000..422b35f38 --- /dev/null +++ b/packages/ee/server-only/stripe/is-price-seats-based.ts @@ -0,0 +1,13 @@ +import type { Stripe } from '@documenso/lib/server-only/stripe'; +import { stripe } from '@documenso/lib/server-only/stripe'; + +export const isPriceSeatsBased = async (priceId: string) => { + const foundStripePrice = await stripe.prices.retrieve(priceId, { + expand: ['product'], + }); + + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const product = foundStripePrice.product as Stripe.Product; + + return product.metadata.isSeatBased === 'true'; +}; diff --git a/packages/ee/server-only/stripe/update-subscription-item-quantity.ts b/packages/ee/server-only/stripe/update-subscription-item-quantity.ts index e0fa95f3d..a089c8ce6 100644 --- a/packages/ee/server-only/stripe/update-subscription-item-quantity.ts +++ b/packages/ee/server-only/stripe/update-subscription-item-quantity.ts @@ -1,6 +1,12 @@ +import type { OrganisationClaim, Subscription } from '@prisma/client'; import type Stripe from 'stripe'; +import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import { stripe } from '@documenso/lib/server-only/stripe'; +import { appLog } from '@documenso/lib/utils/debugger'; +import { prisma } from '@documenso/prisma'; + +import { isPriceSeatsBased } from './is-price-seats-based'; export type UpdateSubscriptionItemQuantityOptions = { subscriptionId: string; @@ -42,3 +48,57 @@ export const updateSubscriptionItemQuantity = async ({ await stripe.subscriptions.update(subscriptionId, subscriptionUpdatePayload); }; + +/** + * Checks whether the member count should be synced with a given Stripe subscription. + * + * If the subscription is not "seat" based, it will be ignored. + * + * @param subscription - The subscription to sync the member count with. + * @param organisationClaim - The organisation claim + * @param quantity - The amount to sync the Stripe item with + * @returns + */ +export const syncMemberCountWithStripeSeatPlan = async ( + subscription: Subscription, + organisationClaim: OrganisationClaim, + quantity: number, +) => { + const maximumMemberCount = organisationClaim.memberCount; + + // Infinite seats means no sync needed. + if (maximumMemberCount === 0) { + return; + } + + const syncMemberCountWithStripe = await isPriceSeatsBased(subscription.priceId); + + // Throw error if quantity exceeds maximum member count and the subscription is not seats based. + if (quantity > maximumMemberCount && !syncMemberCountWithStripe) { + throw new AppError(AppErrorCode.LIMIT_EXCEEDED, { + message: 'Maximum member count reached', + }); + } + + // Bill the user with the new quantity. + if (syncMemberCountWithStripe) { + appLog('BILLING', 'Updating seat based plan'); + + await updateSubscriptionItemQuantity({ + priceId: subscription.priceId, + subscriptionId: subscription.planId, + quantity, + }); + + // This should be automatically updated after the Stripe webhook is fired + // but we just manually adjust it here as well to avoid any race conditions. + await prisma.organisationClaim.update({ + where: { + id: organisationClaim.id, + }, + data: { + memberCount: quantity, + }, + }); + } +}; diff --git a/packages/ee/server-only/stripe/webhook/handler.ts b/packages/ee/server-only/stripe/webhook/handler.ts index ce6e5b936..6ce377e02 100644 --- a/packages/ee/server-only/stripe/webhook/handler.ts +++ b/packages/ee/server-only/stripe/webhook/handler.ts @@ -1,13 +1,11 @@ import { match } from 'ts-pattern'; import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app'; -import { STRIPE_PLAN_TYPE } from '@documenso/lib/constants/billing'; import type { Stripe } from '@documenso/lib/server-only/stripe'; import { stripe } from '@documenso/lib/server-only/stripe'; -import { createTeamFromPendingTeam } from '@documenso/lib/server-only/team/create-team'; import { env } from '@documenso/lib/utils/env'; -import { prisma } from '@documenso/prisma'; +import { onSubscriptionCreated } from './on-subscription-created'; import { onSubscriptionDeleted } from './on-subscription-deleted'; import { onSubscriptionUpdated } from './on-subscription-updated'; @@ -65,78 +63,18 @@ export const stripeWebhookHandler = async (req: Request): Promise => { const event = stripe.webhooks.constructEvent(payload, signature, webhookSecret); + /** + * Notes: + * - Dropped invoice.payment_succeeded + * - Dropped invoice.payment_failed + * - Dropped checkout-session.completed + */ return await match(event.type) - .with('checkout.session.completed', async () => { + .with('customer.subscription.created', async () => { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const session = event.data.object as Stripe.Checkout.Session; + const subscription = event.data.object as Stripe.Subscription; - const customerId = - typeof session.customer === 'string' ? session.customer : session.customer?.id; - - // Attempt to get the user ID from the client reference id. - let userId = Number(session.client_reference_id); - - // If the user ID is not found, attempt to get it from the Stripe customer metadata. - if (!userId && customerId) { - const customer = await stripe.customers.retrieve(customerId); - - if (!customer.deleted) { - userId = Number(customer.metadata.userId); - } - } - - // Finally, attempt to get the user ID from the subscription within the database. - if (!userId && customerId) { - const result = await prisma.user.findFirst({ - select: { - id: true, - }, - where: { - customerId, - }, - }); - - if (result?.id) { - userId = result.id; - } - } - - const subscriptionId = - typeof session.subscription === 'string' - ? session.subscription - : session.subscription?.id; - - if (!subscriptionId) { - return Response.json( - { success: false, message: 'Invalid session' } satisfies StripeWebhookResponse, - { status: 500 }, - ); - } - - const subscription = await stripe.subscriptions.retrieve(subscriptionId); - - // Handle team creation after seat checkout. - if (subscription.items.data[0].price.metadata.plan === STRIPE_PLAN_TYPE.TEAM) { - await handleTeamSeatCheckout({ subscription }); - - return Response.json( - { success: true, message: 'Webhook received' } satisfies StripeWebhookResponse, - { status: 200 }, - ); - } - - // Validate user ID. - if (!userId || Number.isNaN(userId)) { - return Response.json( - { - success: false, - message: 'Invalid session or missing user ID', - } satisfies StripeWebhookResponse, - { status: 500 }, - ); - } - - await onSubscriptionUpdated({ userId, subscription }); + await onSubscriptionCreated({ subscription }); return Response.json( { success: true, message: 'Webhook received' } satisfies StripeWebhookResponse, @@ -147,254 +85,14 @@ export const stripeWebhookHandler = async (req: Request): Promise => { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const subscription = event.data.object as Stripe.Subscription; - const customerId = - typeof subscription.customer === 'string' - ? subscription.customer - : subscription.customer.id; - - if (subscription.items.data[0].price.metadata.plan === STRIPE_PLAN_TYPE.TEAM) { - const team = await prisma.team.findFirst({ - where: { - customerId, - }, - }); - - if (!team) { - return Response.json( - { - success: false, - message: 'No team associated with subscription found', - } satisfies StripeWebhookResponse, - { status: 500 }, - ); - } - - await onSubscriptionUpdated({ teamId: team.id, subscription }); - - return Response.json( - { success: true, message: 'Webhook received' } satisfies StripeWebhookResponse, - { status: 200 }, - ); - } - - const result = await prisma.user.findFirst({ - select: { - id: true, - }, - where: { - customerId, - }, - }); - - if (!result?.id) { - return Response.json( - { - success: false, - message: 'User not found', - } satisfies StripeWebhookResponse, - { status: 500 }, - ); - } - - await onSubscriptionUpdated({ userId: result.id, subscription }); - - return Response.json( - { - success: true, - message: 'Webhook received', - } satisfies StripeWebhookResponse, - { status: 200 }, - ); - }) - .with('invoice.payment_succeeded', async () => { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const invoice = event.data.object as Stripe.Invoice; + const previousAttributes = event.data + .previous_attributes as Partial | null; - if (invoice.billing_reason !== 'subscription_cycle') { - return Response.json( - { - success: true, - message: 'Webhook received', - } satisfies StripeWebhookResponse, - { status: 200 }, - ); - } - - const customerId = - typeof invoice.customer === 'string' ? invoice.customer : invoice.customer?.id; - - const subscriptionId = - typeof invoice.subscription === 'string' - ? invoice.subscription - : invoice.subscription?.id; - - if (!customerId || !subscriptionId) { - return Response.json( - { - success: false, - message: 'Invalid invoice', - } satisfies StripeWebhookResponse, - { status: 500 }, - ); - } - - const subscription = await stripe.subscriptions.retrieve(subscriptionId); - - if (subscription.status === 'incomplete_expired') { - return Response.json( - { - success: true, - message: 'Webhook received', - } satisfies StripeWebhookResponse, - { status: 200 }, - ); - } - - if (subscription.items.data[0].price.metadata.plan === STRIPE_PLAN_TYPE.TEAM) { - const team = await prisma.team.findFirst({ - where: { - customerId, - }, - }); - - if (!team) { - return Response.json( - { - success: false, - message: 'No team associated with subscription found', - } satisfies StripeWebhookResponse, - { status: 500 }, - ); - } - - await onSubscriptionUpdated({ teamId: team.id, subscription }); - - return Response.json( - { - success: true, - message: 'Webhook received', - } satisfies StripeWebhookResponse, - { status: 200 }, - ); - } - - const result = await prisma.user.findFirst({ - select: { - id: true, - }, - where: { - customerId, - }, - }); - - if (!result?.id) { - return Response.json( - { - success: false, - message: 'User not found', - } satisfies StripeWebhookResponse, - { status: 500 }, - ); - } - - await onSubscriptionUpdated({ userId: result.id, subscription }); + await onSubscriptionUpdated({ subscription, previousAttributes }); return Response.json( - { - success: true, - message: 'Webhook received', - } satisfies StripeWebhookResponse, - { status: 200 }, - ); - }) - .with('invoice.payment_failed', async () => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const invoice = event.data.object as Stripe.Invoice; - - const customerId = - typeof invoice.customer === 'string' ? invoice.customer : invoice.customer?.id; - - const subscriptionId = - typeof invoice.subscription === 'string' - ? invoice.subscription - : invoice.subscription?.id; - - if (!customerId || !subscriptionId) { - return Response.json( - { - success: false, - message: 'Invalid invoice', - } satisfies StripeWebhookResponse, - { status: 500 }, - ); - } - - const subscription = await stripe.subscriptions.retrieve(subscriptionId); - - if (subscription.status === 'incomplete_expired') { - return Response.json( - { - success: true, - message: 'Webhook received', - } satisfies StripeWebhookResponse, - { status: 200 }, - ); - } - - if (subscription.items.data[0].price.metadata.plan === STRIPE_PLAN_TYPE.TEAM) { - const team = await prisma.team.findFirst({ - where: { - customerId, - }, - }); - - if (!team) { - return Response.json( - { - success: false, - message: 'No team associated with subscription found', - } satisfies StripeWebhookResponse, - { status: 500 }, - ); - } - - await onSubscriptionUpdated({ teamId: team.id, subscription }); - - return Response.json( - { - success: true, - message: 'Webhook received', - } satisfies StripeWebhookResponse, - { status: 200 }, - ); - } - - const result = await prisma.user.findFirst({ - select: { - id: true, - }, - where: { - customerId, - }, - }); - - if (!result?.id) { - return Response.json( - { - success: false, - message: 'User not found', - } satisfies StripeWebhookResponse, - { status: 500 }, - ); - } - - await onSubscriptionUpdated({ userId: result.id, subscription }); - - return Response.json( - { - success: true, - message: 'Webhook received', - } satisfies StripeWebhookResponse, + { success: true, message: 'Webhook received' } satisfies StripeWebhookResponse, { status: 200 }, ); }) @@ -424,6 +122,13 @@ export const stripeWebhookHandler = async (req: Request): Promise => { } catch (err) { console.error(err); + if (err instanceof Response) { + const message = await err.json(); + console.error(message); + + return err; + } + return Response.json( { success: false, @@ -433,21 +138,3 @@ export const stripeWebhookHandler = async (req: Request): Promise => { ); } }; - -export type HandleTeamSeatCheckoutOptions = { - subscription: Stripe.Subscription; -}; - -const handleTeamSeatCheckout = async ({ subscription }: HandleTeamSeatCheckoutOptions) => { - if (subscription.metadata?.pendingTeamId === undefined) { - throw new Error('Missing pending team ID'); - } - - const pendingTeamId = Number(subscription.metadata.pendingTeamId); - - if (Number.isNaN(pendingTeamId)) { - throw new Error('Invalid pending team ID'); - } - - return await createTeamFromPendingTeam({ pendingTeamId, subscription }).then((team) => team.id); -}; diff --git a/packages/ee/server-only/stripe/webhook/on-subscription-created.ts b/packages/ee/server-only/stripe/webhook/on-subscription-created.ts new file mode 100644 index 000000000..7b26f23d2 --- /dev/null +++ b/packages/ee/server-only/stripe/webhook/on-subscription-created.ts @@ -0,0 +1,185 @@ +import type { SubscriptionClaim } from '@prisma/client'; +import { SubscriptionStatus } from '@prisma/client'; +import { match } from 'ts-pattern'; + +import { createOrganisation } from '@documenso/lib/server-only/organisation/create-organisation'; +import { type Stripe } from '@documenso/lib/server-only/stripe'; +import type { StripeOrganisationCreateMetadata } from '@documenso/lib/types/subscription'; +import { ZStripeOrganisationCreateMetadataSchema } from '@documenso/lib/types/subscription'; +import { prisma } from '@documenso/prisma'; + +import { createOrganisationClaimUpsertData, extractStripeClaim } from './on-subscription-updated'; + +export type OnSubscriptionCreatedOptions = { + subscription: Stripe.Subscription; +}; + +type StripeWebhookResponse = { + success: boolean; + message: string; +}; + +export const onSubscriptionCreated = async ({ subscription }: OnSubscriptionCreatedOptions) => { + const customerId = + typeof subscription.customer === 'string' ? subscription.customer : subscription.customer.id; + + // Todo: logging + if (subscription.items.data.length !== 1) { + console.error('No support for multiple items'); + + throw Response.json( + { + success: false, + message: 'No support for multiple items', + } satisfies StripeWebhookResponse, + { status: 500 }, + ); + } + + const organisationId = await handleOrganisationCreateOrGet({ + subscription, + customerId, + }); + + const subscriptionItem = subscription.items.data[0]; + const subscriptionClaim = await extractStripeClaim(subscriptionItem.price); + + // Todo: logging + if (!subscriptionClaim) { + console.error(`Subscription claim on ${subscriptionItem.price.id} not found`); + + throw Response.json( + { + success: false, + message: `Subscription claim on ${subscriptionItem.price.id} not found`, + } satisfies StripeWebhookResponse, + { status: 500 }, + ); + } + + await handleSubscriptionCreate({ + subscription, + customerId, + organisationId, + subscriptionClaim, + }); +}; + +type HandleSubscriptionCreateOptions = { + subscription: Stripe.Subscription; + customerId: string; + organisationId: string; + subscriptionClaim: SubscriptionClaim; +}; + +const handleSubscriptionCreate = async ({ + subscription, + customerId, + organisationId, + subscriptionClaim, +}: HandleSubscriptionCreateOptions) => { + const status = match(subscription.status) + .with('active', () => SubscriptionStatus.ACTIVE) + .with('past_due', () => SubscriptionStatus.PAST_DUE) + .otherwise(() => SubscriptionStatus.INACTIVE); + + await prisma.$transaction(async (tx) => { + await tx.subscription.create({ + data: { + organisationId, + status: status, + planId: subscription.id, + priceId: subscription.items.data[0].price.id, + periodEnd: new Date(subscription.current_period_end * 1000), + cancelAtPeriodEnd: subscription.cancel_at_period_end, + customerId, + }, + }); + + await tx.organisationClaim.create({ + data: { + organisation: { + connect: { + id: organisationId, + }, + }, + originalSubscriptionClaimId: subscriptionClaim.id, + ...createOrganisationClaimUpsertData(subscriptionClaim), + }, + }); + }); +}; + +type HandleOrganisationCreateOrGetOptions = { + subscription: Stripe.Subscription; + customerId: string; +}; + +const handleOrganisationCreateOrGet = async ({ + subscription, + customerId, +}: HandleOrganisationCreateOrGetOptions) => { + let organisationCreateFlowData: StripeOrganisationCreateMetadata | null = null; + + if (subscription.metadata?.organisationCreateData) { + const parseResult = ZStripeOrganisationCreateMetadataSchema.safeParse( + JSON.parse(subscription.metadata.organisationCreateData), + ); + + if (!parseResult.success) { + console.error('Invalid organisation create flow data'); + + throw Response.json( + { + success: false, + message: 'Invalid organisation create flow data', + } satisfies StripeWebhookResponse, + { status: 500 }, + ); + } + + organisationCreateFlowData = parseResult.data; + + const createdOrganisation = await createOrganisation({ + name: organisationCreateFlowData.organisationName, + userId: organisationCreateFlowData.userId, + }); + + return createdOrganisation.id; + } + + const organisation = await prisma.organisation.findFirst({ + where: { + customerId, + }, + include: { + subscription: true, + }, + }); + + if (!organisation) { + throw Response.json( + { + success: false, + message: `Organisation not found`, + } satisfies StripeWebhookResponse, + { status: 500 }, + ); + } + + // Todo: logging + if (organisation.subscription) { + console.error('Organisation already has a subscription'); + + // This should never happen + throw Response.json( + { + success: false, + message: `Organisation already has a subscription`, + } satisfies StripeWebhookResponse, + { status: 500 }, + ); + } + + return organisation.id; +}; diff --git a/packages/ee/server-only/stripe/webhook/on-subscription-updated.ts b/packages/ee/server-only/stripe/webhook/on-subscription-updated.ts index 30a5d1910..293b5365f 100644 --- a/packages/ee/server-only/stripe/webhook/on-subscription-updated.ts +++ b/packages/ee/server-only/stripe/webhook/on-subscription-updated.ts @@ -1,59 +1,178 @@ -import type { Prisma } from '@prisma/client'; +import type { Prisma, SubscriptionClaim } from '@prisma/client'; import { SubscriptionStatus } from '@prisma/client'; import { match } from 'ts-pattern'; -import type { Stripe } from '@documenso/lib/server-only/stripe'; +import { type Stripe, stripe } from '@documenso/lib/server-only/stripe'; import { prisma } from '@documenso/prisma'; export type OnSubscriptionUpdatedOptions = { - userId?: number; - teamId: number; subscription: Stripe.Subscription; + previousAttributes: Partial | null; }; +type StripeWebhookResponse = { + success: boolean; + message: string; +}; + +// Todo: orgs check if the subscription matches since it's possible to have more than one (via stripe) export const onSubscriptionUpdated = async ({ - userId, - teamId, subscription, + previousAttributes, }: OnSubscriptionUpdatedOptions) => { - await prisma.subscription.upsert( - mapStripeSubscriptionToPrismaUpsertAction(subscription, userId, teamId), - ); -}; + const customerId = + typeof subscription.customer === 'string' ? subscription.customer : subscription.customer.id; -export const mapStripeSubscriptionToPrismaUpsertAction = ( - subscription: Stripe.Subscription, - userId?: number, - teamId?: number, -): Prisma.SubscriptionUpsertArgs => { - if ((!userId && !teamId) || (userId && teamId)) { - throw new Error('Either userId or teamId must be provided.'); + // Todo: logging + if (subscription.items.data.length !== 1) { + console.error('No support for multiple items'); + + throw Response.json( + { + success: false, + message: 'No support for multiple items', + } satisfies StripeWebhookResponse, + { status: 500 }, + ); } + const organisation = await prisma.organisation.findFirst({ + where: { + customerId, + }, + include: { + organisationClaim: true, + }, + }); + + if (!organisation) { + throw Response.json( + { + success: false, + message: `Organisation not found`, + } satisfies StripeWebhookResponse, + { status: 500 }, + ); + } + + const previousItem = previousAttributes?.items?.data[0]; + const updatedItem = subscription.items.data[0]; + + const previousSubscriptionClaimId = previousItem + ? await extractStripeClaimId(previousItem.price) + : null; + const updatedSubscriptionClaim = await extractStripeClaim(updatedItem.price); + + if (!updatedSubscriptionClaim) { + console.error(`Subscription claim on ${updatedItem.price.id} not found`); + + throw Response.json( + { + success: false, + message: `Subscription claim on ${updatedItem.price.id} not found`, + } satisfies StripeWebhookResponse, + { status: 500 }, + ); + } + + const newClaimFound = previousSubscriptionClaimId !== updatedSubscriptionClaim.id; + const status = match(subscription.status) .with('active', () => SubscriptionStatus.ACTIVE) .with('past_due', () => SubscriptionStatus.PAST_DUE) .otherwise(() => SubscriptionStatus.INACTIVE); + await prisma.$transaction(async (tx) => { + await tx.subscription.update({ + where: { + planId: subscription.id, + }, + data: { + organisationId: organisation.id, + status: status, + planId: subscription.id, + priceId: subscription.items.data[0].price.id, + periodEnd: new Date(subscription.current_period_end * 1000), + cancelAtPeriodEnd: subscription.cancel_at_period_end, + customerId, + }, + }); + + // Override current organisation claim if new one is found. + if (newClaimFound) { + await tx.organisationClaim.update({ + where: { + id: organisation.organisationClaim.id, + }, + data: { + originalSubscriptionClaimId: updatedSubscriptionClaim.id, + ...createOrganisationClaimUpsertData(updatedSubscriptionClaim), + }, + }); + } + }); +}; + +export const createOrganisationClaimUpsertData = (subscriptionClaim: SubscriptionClaim) => { + // Done like this to ensure type errors are thrown if items are added. + const data: Omit< + Prisma.SubscriptionClaimCreateInput, + 'id' | 'createdAt' | 'updatedAt' | 'locked' | 'name' + > = { + flags: { + ...subscriptionClaim.flags, + }, + teamCount: subscriptionClaim.teamCount, + memberCount: subscriptionClaim.memberCount, + }; + return { - where: { - planId: subscription.id, - }, - create: { - status: status, - planId: subscription.id, - priceId: subscription.items.data[0].price.id, - periodEnd: new Date(subscription.current_period_end * 1000), - userId: userId ?? null, - teamId: teamId ?? null, - cancelAtPeriodEnd: subscription.cancel_at_period_end, - }, - update: { - status: status, - planId: subscription.id, - priceId: subscription.items.data[0].price.id, - periodEnd: new Date(subscription.current_period_end * 1000), - cancelAtPeriodEnd: subscription.cancel_at_period_end, - }, + ...data, }; }; + +/** + * Checks the price metadata for a claimId, if it is missing it will fetch + * and check the product metadata for a claimId. + * + * The order of priority is: + * 1. Price metadata + * 2. Product metadata + * + * @returns The claimId or null if no claimId is found. + */ +export const extractStripeClaimId = async (priceId: Stripe.Price) => { + if (priceId.metadata.claimId) { + return priceId.metadata.claimId; + } + + const productId = typeof priceId.product === 'string' ? priceId.product : priceId.product.id; + + const product = await stripe.products.retrieve(productId); + + return product.metadata.claimId || null; +}; + +/** + * Checks the price metadata for a claimId, if it is missing it will fetch + * and check the product metadata for a claimId. + * + */ +export const extractStripeClaim = async (priceId: Stripe.Price) => { + const claimId = await extractStripeClaimId(priceId); + + if (!claimId) { + return null; + } + + const subscriptionClaim = await prisma.subscriptionClaim.findFirst({ + where: { id: claimId }, + }); + + if (!subscriptionClaim) { + console.error(`Subscription claim ${claimId} not found`); + return null; + } + + return subscriptionClaim; +}; diff --git a/packages/ee/server-only/util/is-community-plan.ts b/packages/ee/server-only/util/is-community-plan.ts deleted file mode 100644 index ccf64562d..000000000 --- a/packages/ee/server-only/util/is-community-plan.ts +++ /dev/null @@ -1,57 +0,0 @@ -import type { Subscription } from '@prisma/client'; - -import { subscriptionsContainsActivePlan } from '@documenso/lib/utils/billing'; -import { prisma } from '@documenso/prisma'; - -import { getCommunityPlanPriceIds } from '../stripe/get-community-plan-prices'; - -export type IsCommunityPlanOptions = { - userId: number; - teamId: number; -}; - -/** - * Whether the user or team is on the community plan. - */ -export const isCommunityPlan = async ({ - userId, - teamId, -}: IsCommunityPlanOptions): Promise => { - let subscriptions: Subscription[] = []; - - if (teamId) { - subscriptions = await prisma.team - .findFirstOrThrow({ - where: { - id: teamId, - }, - select: { - owner: { - include: { - subscriptions: true, - }, - }, - }, - }) - .then((team) => team.owner.subscriptions); - } else { - subscriptions = await prisma.user - .findFirstOrThrow({ - where: { - id: userId, - }, - select: { - subscriptions: true, - }, - }) - .then((user) => user.subscriptions); - } - - if (subscriptions.length === 0) { - return false; - } - - const communityPlanPriceIds = await getCommunityPlanPriceIds(); - - return subscriptionsContainsActivePlan(subscriptions, communityPlanPriceIds); -}; diff --git a/packages/ee/server-only/util/is-document-enterprise.ts b/packages/ee/server-only/util/is-document-enterprise.ts deleted file mode 100644 index 4bc1a71f8..000000000 --- a/packages/ee/server-only/util/is-document-enterprise.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { Subscription } from '@prisma/client'; - -import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app'; -import { subscriptionsContainsActivePlan } from '@documenso/lib/utils/billing'; -import { prisma } from '@documenso/prisma'; - -import { getEnterprisePlanPriceIds } from '../stripe/get-enterprise-plan-prices'; - -export type IsUserEnterpriseOptions = { - userId: number; - teamId: number; -}; - -/** - * Whether the user is enterprise, or has permission to use enterprise features on - * behalf of their team. - * - * It is assumed that the provided user is part of the provided team. - */ -export const isUserEnterprise = async ({ - userId, - teamId, -}: IsUserEnterpriseOptions): Promise => { - let subscriptions: Subscription[] = []; - - if (!IS_BILLING_ENABLED()) { - return false; - } - - if (teamId) { - subscriptions = await prisma.team - .findFirstOrThrow({ - where: { - id: teamId, - }, - select: { - owner: { - include: { - subscriptions: true, - }, - }, - }, - }) - .then((team) => team.owner.subscriptions); - } else { - subscriptions = await prisma.user - .findFirstOrThrow({ - where: { - id: userId, - }, - select: { - subscriptions: true, - }, - }) - .then((user) => user.subscriptions); - } - - if (subscriptions.length === 0) { - return false; - } - - const enterprisePlanPriceIds = await getEnterprisePlanPriceIds(); - - return subscriptionsContainsActivePlan(subscriptions, enterprisePlanPriceIds, true); -}; diff --git a/packages/ee/server-only/util/is-document-platform.ts b/packages/ee/server-only/util/is-document-platform.ts deleted file mode 100644 index 6a0dbbb61..000000000 --- a/packages/ee/server-only/util/is-document-platform.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { Document, Subscription } from '@prisma/client'; - -import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app'; -import { subscriptionsContainsActivePlan } from '@documenso/lib/utils/billing'; -import { prisma } from '@documenso/prisma'; - -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: { - subscriptions: true, - }, - }, - }, - }) - .then((team) => team.owner.subscriptions); - } else { - subscriptions = await prisma.user - .findFirstOrThrow({ - where: { - id: userId, - }, - select: { - subscriptions: true, - }, - }) - .then((user) => user.subscriptions); - } - - if (subscriptions.length === 0) { - return false; - } - - const platformPlanPriceIds = await getPlatformPlanPriceIds(); - - return subscriptionsContainsActivePlan(subscriptions, platformPlanPriceIds); -}; diff --git a/packages/email/templates/organisation-invite.tsx b/packages/email/templates/organisation-invite.tsx index 4dc2f713d..e959023e0 100644 --- a/packages/email/templates/organisation-invite.tsx +++ b/packages/email/templates/organisation-invite.tsx @@ -23,7 +23,6 @@ export type OrganisationInviteEmailProps = { baseUrl: string; senderName: string; organisationName: string; - teamName?: string; token: string; }; @@ -32,13 +31,12 @@ export const OrganisationInviteEmailTemplate = ({ baseUrl = 'https://documenso.com', senderName = 'John Doe', organisationName = 'Organisation Name', - teamName = 'Team Name', token = '', }: OrganisationInviteEmailProps) => { const { _ } = useLingui(); const branding = useBranding(); - const previewText = msg`Accept invitation to join a team on Documenso`; + const previewText = msg`Accept invitation to join an organisation on Documenso`; return ( @@ -72,15 +70,11 @@ export const OrganisationInviteEmailTemplate = ({ - {teamName ? ( - You have been invited to join the following team - ) : ( - You have been invited to join the following organisation - )} + You have been invited to join the following organisation
- {teamName || organisationName} + {organisationName}
@@ -92,13 +86,13 @@ export const OrganisationInviteEmailTemplate = ({
diff --git a/packages/email/templates/team-join.tsx b/packages/email/templates/organisation-join.tsx similarity index 75% rename from packages/email/templates/team-join.tsx rename to packages/email/templates/organisation-join.tsx index d9dbe7ff9..8690bd5c9 100644 --- a/packages/email/templates/team-join.tsx +++ b/packages/email/templates/organisation-join.tsx @@ -2,34 +2,32 @@ import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; -import { formatTeamUrl } from '@documenso/lib/utils/teams'; - import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components'; import { useBranding } from '../providers/branding'; import { TemplateFooter } from '../template-components/template-footer'; import TemplateImage from '../template-components/template-image'; -export type TeamJoinEmailProps = { +export type OrganisationJoinEmailProps = { assetBaseUrl: string; baseUrl: string; memberName: string; memberEmail: string; - teamName: string; - teamUrl: string; + organisationName: string; + organisationUrl: string; }; -export const TeamJoinEmailTemplate = ({ +export const OrganisationJoinEmailTemplate = ({ assetBaseUrl = 'http://localhost:3002', baseUrl = 'https://documenso.com', memberName = 'John Doe', memberEmail = 'johndoe@documenso.com', - teamName = 'Team Name', - teamUrl = 'demo', -}: TeamJoinEmailProps) => { + organisationName = 'Organisation Name', + organisationUrl = 'demo', +}: OrganisationJoinEmailProps) => { const { _ } = useLingui(); const branding = useBranding(); - const previewText = msg`A team member has joined a team on Documenso`; + const previewText = msg`A member has joined your organisation on Documenso`; return ( @@ -59,17 +57,11 @@ export const TeamJoinEmailTemplate = ({
- - {memberName || memberEmail} joined the team {teamName} on Documenso - - - - - {memberEmail} joined the following team + A new member has joined your organisation {organisationName}
- {formatTeamUrl(teamUrl, baseUrl)} + {memberName || memberEmail}
@@ -85,4 +77,4 @@ export const TeamJoinEmailTemplate = ({ ); }; -export default TeamJoinEmailTemplate; +export default OrganisationJoinEmailTemplate; diff --git a/packages/email/templates/team-leave.tsx b/packages/email/templates/organisation-leave.tsx similarity index 75% rename from packages/email/templates/team-leave.tsx rename to packages/email/templates/organisation-leave.tsx index b26016f93..bdc565aa5 100644 --- a/packages/email/templates/team-leave.tsx +++ b/packages/email/templates/organisation-leave.tsx @@ -2,34 +2,32 @@ import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; -import { formatTeamUrl } from '@documenso/lib/utils/teams'; - import { Body, Container, Head, Hr, Html, Img, Preview, Section, Text } from '../components'; import { useBranding } from '../providers/branding'; import { TemplateFooter } from '../template-components/template-footer'; import TemplateImage from '../template-components/template-image'; -export type TeamLeaveEmailProps = { +export type OrganisationLeaveEmailProps = { assetBaseUrl: string; baseUrl: string; memberName: string; memberEmail: string; - teamName: string; - teamUrl: string; + organisationName: string; + organisationUrl: string; }; -export const TeamLeaveEmailTemplate = ({ +export const OrganisationLeaveEmailTemplate = ({ assetBaseUrl = 'http://localhost:3002', baseUrl = 'https://documenso.com', memberName = 'John Doe', memberEmail = 'johndoe@documenso.com', - teamName = 'Team Name', - teamUrl = 'demo', -}: TeamLeaveEmailProps) => { + organisationName = 'Organisation Name', + organisationUrl = 'demo', +}: OrganisationLeaveEmailProps) => { const { _ } = useLingui(); const branding = useBranding(); - const previewText = msg`A team member has left a team on Documenso`; + const previewText = msg`A member has left your organisation on Documenso`; return ( @@ -59,17 +57,11 @@ export const TeamLeaveEmailTemplate = ({
- - {memberName || memberEmail} left the team {teamName} on Documenso - - - - - {memberEmail} left the following team + A member has left your organisation {organisationName}
- {formatTeamUrl(teamUrl, baseUrl)} + {memberName || memberEmail}
@@ -85,4 +77,4 @@ export const TeamLeaveEmailTemplate = ({ ); }; -export default TeamLeaveEmailTemplate; +export default OrganisationLeaveEmailTemplate; diff --git a/apps/remix/app/providers/organisation.tsx b/packages/lib/client-only/providers/organisation.tsx similarity index 100% rename from apps/remix/app/providers/organisation.tsx rename to packages/lib/client-only/providers/organisation.tsx diff --git a/packages/lib/constants/billing.ts b/packages/lib/constants/billing.ts index b79610dff..48c16802d 100644 --- a/packages/lib/constants/billing.ts +++ b/packages/lib/constants/billing.ts @@ -1,12 +1,18 @@ -export enum STRIPE_CUSTOMER_TYPE { - INDIVIDUAL = 'individual', - ORGANISATION = 'organisation', -} +import { SubscriptionStatus } from '@prisma/client'; export enum STRIPE_PLAN_TYPE { - REGULAR = 'regular', - TEAM = 'team', - COMMUNITY = 'community', + FREE = 'free', + INDIVIDUAL = 'individual', + PRO = 'pro', + EARLY_ADOPTER = 'earlyAdopter', PLATFORM = 'platform', ENTERPRISE = 'enterprise', } + +export const FREE_TIER_DOCUMENT_QUOTA = 5; + +export const SUBSCRIPTION_STATUS_MAP = { + [SubscriptionStatus.ACTIVE]: 'Active', + [SubscriptionStatus.INACTIVE]: 'Inactive', + [SubscriptionStatus.PAST_DUE]: 'Past Due', +}; diff --git a/packages/lib/constants/feature-flags.ts b/packages/lib/constants/feature-flags.ts index 50d1ff6b5..f5b6a406a 100644 --- a/packages/lib/constants/feature-flags.ts +++ b/packages/lib/constants/feature-flags.ts @@ -2,7 +2,6 @@ import { env } from '@documenso/lib/utils/env'; import { NEXT_PUBLIC_WEBAPP_URL } from './app'; -const NEXT_PUBLIC_FEATURE_BILLING_ENABLED = () => env('NEXT_PUBLIC_FEATURE_BILLING_ENABLED'); const NEXT_PUBLIC_POSTHOG_KEY = () => env('NEXT_PUBLIC_POSTHOG_KEY'); /** @@ -10,26 +9,6 @@ const NEXT_PUBLIC_POSTHOG_KEY = () => env('NEXT_PUBLIC_POSTHOG_KEY'); */ export const FEATURE_FLAG_GLOBAL_SESSION_RECORDING = 'global_session_recording'; -/** - * How frequent to poll for new feature flags in milliseconds. - */ -export const FEATURE_FLAG_POLL_INTERVAL = 30000; - -/** - * Feature flags that will be used when PostHog is disabled. - * - * Does not take any person or group properties into account. - */ -export const LOCAL_FEATURE_FLAGS: Record = { - app_allow_encrypted_documents: false, - app_billing: NEXT_PUBLIC_FEATURE_BILLING_ENABLED() === 'true', - app_document_page_view_history_sheet: false, - app_passkey: true, - app_public_profile: true, - marketing_header_single_player_mode: false, - marketing_profiles_announcement_bar: true, -} as const; - /** * Extract the PostHog configuration from the environment. */ @@ -46,10 +25,3 @@ export function extractPostHogConfig(): { key: string; host: string } | null { host: postHogHost, }; } - -/** - * Whether feature flags are enabled for the current instance. - */ -export function isFeatureFlagEnabled(): boolean { - return extractPostHogConfig() !== null; -} diff --git a/packages/lib/jobs/client.ts b/packages/lib/jobs/client.ts index 713d928d8..fd06c532b 100644 --- a/packages/lib/jobs/client.ts +++ b/packages/lib/jobs/client.ts @@ -1,13 +1,13 @@ import { JobClient } from './client/client'; import { SEND_CONFIRMATION_EMAIL_JOB_DEFINITION } from './definitions/emails/send-confirmation-email'; import { SEND_DOCUMENT_CANCELLED_EMAILS_JOB_DEFINITION } from './definitions/emails/send-document-cancelled-emails'; +import { SEND_ORGANISATION_MEMBER_JOINED_EMAIL_JOB_DEFINITION } from './definitions/emails/send-organisation-member-joined-email'; +import { SEND_ORGANISATION_MEMBER_LEFT_EMAIL_JOB_DEFINITION } from './definitions/emails/send-organisation-member-left-email'; import { SEND_PASSWORD_RESET_SUCCESS_EMAIL_JOB_DEFINITION } from './definitions/emails/send-password-reset-success-email'; import { SEND_RECIPIENT_SIGNED_EMAIL_JOB_DEFINITION } from './definitions/emails/send-recipient-signed-email'; import { SEND_SIGNING_REJECTION_EMAILS_JOB_DEFINITION } from './definitions/emails/send-rejection-emails'; import { SEND_SIGNING_EMAIL_JOB_DEFINITION } from './definitions/emails/send-signing-email'; import { SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION } from './definitions/emails/send-team-deleted-email'; -import { SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION } from './definitions/emails/send-team-member-joined-email'; -import { SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION } from './definitions/emails/send-team-member-left-email'; import { BULK_SEND_TEMPLATE_JOB_DEFINITION } from './definitions/internal/bulk-send-template'; import { SEAL_DOCUMENT_JOB_DEFINITION } from './definitions/internal/seal-document'; @@ -18,8 +18,8 @@ import { SEAL_DOCUMENT_JOB_DEFINITION } from './definitions/internal/seal-docume export const jobsClient = new JobClient([ SEND_SIGNING_EMAIL_JOB_DEFINITION, SEND_CONFIRMATION_EMAIL_JOB_DEFINITION, - SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION, - SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION, + SEND_ORGANISATION_MEMBER_JOINED_EMAIL_JOB_DEFINITION, + SEND_ORGANISATION_MEMBER_LEFT_EMAIL_JOB_DEFINITION, SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION, SEAL_DOCUMENT_JOB_DEFINITION, SEND_PASSWORD_RESET_SUCCESS_EMAIL_JOB_DEFINITION, diff --git a/packages/lib/jobs/definitions/emails/send-team-member-joined-email.handler.ts b/packages/lib/jobs/definitions/emails/send-organisation-member-joined-email.handler.ts similarity index 52% rename from packages/lib/jobs/definitions/emails/send-team-member-joined-email.handler.ts rename to packages/lib/jobs/definitions/emails/send-organisation-member-joined-email.handler.ts index b04d48c5c..af78f58bc 100644 --- a/packages/lib/jobs/definitions/emails/send-team-member-joined-email.handler.ts +++ b/packages/lib/jobs/definitions/emails/send-organisation-member-joined-email.handler.ts @@ -1,80 +1,85 @@ import { createElement } from 'react'; import { msg } from '@lingui/core/macro'; -import { TeamMemberRole } from '@prisma/client'; import { mailer } from '@documenso/email/mailer'; -import TeamJoinEmailTemplate from '@documenso/email/templates/team-join'; +import OrganisationJoinEmailTemplate from '@documenso/email/templates/organisation-join'; import { prisma } from '@documenso/prisma'; import { getI18nInstance } from '../../../client-only/providers/i18n-server'; import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app'; import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email'; -import { getTeamSettings } from '../../../server-only/team/get-team-settings'; +import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '../../../constants/organisations'; import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n'; -import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding'; +import { organisationGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding'; import type { JobRunIO } from '../../client/_internal/job'; -import type { TSendTeamMemberJoinedEmailJobDefinition } from './send-team-member-joined-email'; +import type { TSendOrganisationMemberJoinedEmailJobDefinition } from './send-organisation-member-joined-email'; export const run = async ({ payload, io, }: { - payload: TSendTeamMemberJoinedEmailJobDefinition; + payload: TSendOrganisationMemberJoinedEmailJobDefinition; io: JobRunIO; }) => { - const team = await prisma.team.findFirstOrThrow({ + const organisation = await prisma.organisation.findFirstOrThrow({ where: { - id: payload.teamId, + id: payload.organisationId, }, include: { members: { where: { - role: { - in: [TeamMemberRole.ADMIN, TeamMemberRole.MANAGER], + organisationGroupMembers: { + some: { + group: { + organisationRole: { + in: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'], + }, + }, + }, }, }, include: { user: true, }, }, + organisationGlobalSettings: true, }, }); - const settings = await getTeamSettings({ - userId: payload.userId, - teamId: payload.teamId, - }); - - const invitedMember = await prisma.teamMember.findFirstOrThrow({ + const invitedMember = await prisma.organisationMember.findFirstOrThrow({ where: { - id: payload.memberId, - teamId: payload.teamId, + userId: payload.memberUserId, + organisationId: payload.organisationId, }, include: { user: true, }, }); - for (const member of team.members) { + for (const member of organisation.members) { if (member.id === invitedMember.id) { continue; } await io.runTask( - `send-team-member-joined-email--${invitedMember.id}_${member.id}`, + `send-organisation-member-joined-email--${invitedMember.id}_${member.id}`, async () => { - const emailContent = createElement(TeamJoinEmailTemplate, { + const emailContent = createElement(OrganisationJoinEmailTemplate, { assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(), baseUrl: NEXT_PUBLIC_WEBAPP_URL(), memberName: invitedMember.user.name || '', memberEmail: invitedMember.user.email, - teamName: team.name, - teamUrl: team.url, + organisationName: organisation.name, + organisationUrl: organisation.url, }); - const branding = teamGlobalSettingsToBranding(settings, team.id); - const lang = settings.documentLanguage; + const branding = organisationGlobalSettingsToBranding( + organisation.organisationGlobalSettings, + organisation.id, + ); + + const lang = organisation.organisationGlobalSettings.documentLanguage; // !: Replace with the actual language of the recipient later const [html, text] = await Promise.all([ @@ -97,7 +102,7 @@ export const run = async ({ name: FROM_NAME, address: FROM_ADDRESS, }, - subject: i18n._(msg`A new member has joined your team`), + subject: i18n._(msg`A new member has joined your organisation`), html, text, }); diff --git a/packages/lib/jobs/definitions/emails/send-organisation-member-joined-email.ts b/packages/lib/jobs/definitions/emails/send-organisation-member-joined-email.ts new file mode 100644 index 000000000..851ee6b5f --- /dev/null +++ b/packages/lib/jobs/definitions/emails/send-organisation-member-joined-email.ts @@ -0,0 +1,33 @@ +import { z } from 'zod'; + +import type { JobDefinition } from '../../client/_internal/job'; + +const SEND_ORGANISATION_MEMBER_JOINED_EMAIL_JOB_DEFINITION_ID = + 'send.organisation-member-joined.email'; + +const SEND_ORGANISATION_MEMBER_JOINED_EMAIL_JOB_DEFINITION_SCHEMA = z.object({ + organisationId: z.string(), + memberUserId: z.number(), +}); + +export type TSendOrganisationMemberJoinedEmailJobDefinition = z.infer< + typeof SEND_ORGANISATION_MEMBER_JOINED_EMAIL_JOB_DEFINITION_SCHEMA +>; + +export const SEND_ORGANISATION_MEMBER_JOINED_EMAIL_JOB_DEFINITION = { + id: SEND_ORGANISATION_MEMBER_JOINED_EMAIL_JOB_DEFINITION_ID, + name: 'Send Organisation Member Joined Email', + version: '1.0.0', + trigger: { + name: SEND_ORGANISATION_MEMBER_JOINED_EMAIL_JOB_DEFINITION_ID, + schema: SEND_ORGANISATION_MEMBER_JOINED_EMAIL_JOB_DEFINITION_SCHEMA, + }, + handler: async ({ payload, io }) => { + const handler = await import('./send-organisation-member-joined-email.handler'); + + await handler.run({ payload, io }); + }, +} as const satisfies JobDefinition< + typeof SEND_ORGANISATION_MEMBER_JOINED_EMAIL_JOB_DEFINITION_ID, + TSendOrganisationMemberJoinedEmailJobDefinition +>; diff --git a/packages/lib/jobs/definitions/emails/send-organisation-member-left-email.handler.ts b/packages/lib/jobs/definitions/emails/send-organisation-member-left-email.handler.ts new file mode 100644 index 000000000..15cc957e9 --- /dev/null +++ b/packages/lib/jobs/definitions/emails/send-organisation-member-left-email.handler.ts @@ -0,0 +1,107 @@ +import { createElement } from 'react'; + +import { msg } from '@lingui/core/macro'; + +import { mailer } from '@documenso/email/mailer'; +import OrganisationLeaveEmailTemplate from '@documenso/email/templates/organisation-leave'; +import { prisma } from '@documenso/prisma'; + +import { getI18nInstance } from '../../../client-only/providers/i18n-server'; +import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app'; +import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email'; +import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '../../../constants/organisations'; +import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n'; +import { organisationGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding'; +import type { JobRunIO } from '../../client/_internal/job'; +import type { TSendOrganisationMemberLeftEmailJobDefinition } from './send-organisation-member-left-email'; + +export const run = async ({ + payload, + io, +}: { + payload: TSendOrganisationMemberLeftEmailJobDefinition; + io: JobRunIO; +}) => { + const organisation = await prisma.organisation.findFirstOrThrow({ + where: { + id: payload.organisationId, + }, + include: { + members: { + where: { + organisationGroupMembers: { + some: { + group: { + organisationRole: { + in: ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'], + }, + }, + }, + }, + }, + include: { + user: true, + }, + }, + organisationGlobalSettings: true, + }, + }); + + const oldMember = await prisma.user.findFirstOrThrow({ + where: { + id: payload.memberUserId, + }, + }); + + for (const member of organisation.members) { + if (member.userId === oldMember.id) { + continue; + } + + await io.runTask( + `send-organisation-member-left-email--${oldMember.id}_${member.id}`, + async () => { + const emailContent = createElement(OrganisationLeaveEmailTemplate, { + assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(), + baseUrl: NEXT_PUBLIC_WEBAPP_URL(), + memberName: oldMember.name || '', + memberEmail: oldMember.email, + organisationName: organisation.name, + organisationUrl: organisation.url, + }); + + const branding = organisationGlobalSettingsToBranding( + organisation.organisationGlobalSettings, + organisation.id, + ); + + const lang = organisation.organisationGlobalSettings.documentLanguage; + + const [html, text] = await Promise.all([ + renderEmailWithI18N(emailContent, { + lang, + branding, + }), + renderEmailWithI18N(emailContent, { + lang, + branding, + plainText: true, + }), + ]); + + const i18n = await getI18nInstance(lang); + + await mailer.sendMail({ + to: member.user.email, + from: { + name: FROM_NAME, + address: FROM_ADDRESS, + }, + subject: i18n._(msg`A member has left your organisation`), + html, + text, + }); + }, + ); + } +}; diff --git a/packages/lib/jobs/definitions/emails/send-organisation-member-left-email.ts b/packages/lib/jobs/definitions/emails/send-organisation-member-left-email.ts new file mode 100644 index 000000000..f9278688e --- /dev/null +++ b/packages/lib/jobs/definitions/emails/send-organisation-member-left-email.ts @@ -0,0 +1,32 @@ +import { z } from 'zod'; + +import type { JobDefinition } from '../../client/_internal/job'; + +const SEND_ORGANISATION_MEMBER_LEFT_EMAIL_JOB_DEFINITION_ID = 'send.organisation-member-left.email'; + +const SEND_ORGANISATION_MEMBER_LEFT_EMAIL_JOB_DEFINITION_SCHEMA = z.object({ + organisationId: z.string(), + memberUserId: z.number(), +}); + +export type TSendOrganisationMemberLeftEmailJobDefinition = z.infer< + typeof SEND_ORGANISATION_MEMBER_LEFT_EMAIL_JOB_DEFINITION_SCHEMA +>; + +export const SEND_ORGANISATION_MEMBER_LEFT_EMAIL_JOB_DEFINITION = { + id: SEND_ORGANISATION_MEMBER_LEFT_EMAIL_JOB_DEFINITION_ID, + name: 'Send Organisation Member Left Email', + version: '1.0.0', + trigger: { + name: SEND_ORGANISATION_MEMBER_LEFT_EMAIL_JOB_DEFINITION_ID, + schema: SEND_ORGANISATION_MEMBER_LEFT_EMAIL_JOB_DEFINITION_SCHEMA, + }, + handler: async ({ payload, io }) => { + const handler = await import('./send-organisation-member-left-email.handler'); + + await handler.run({ payload, io }); + }, +} as const satisfies JobDefinition< + typeof SEND_ORGANISATION_MEMBER_LEFT_EMAIL_JOB_DEFINITION_ID, + TSendOrganisationMemberLeftEmailJobDefinition +>; diff --git a/packages/lib/jobs/definitions/emails/send-team-deleted-email.ts b/packages/lib/jobs/definitions/emails/send-team-deleted-email.ts index cb0574984..f779566a1 100644 --- a/packages/lib/jobs/definitions/emails/send-team-deleted-email.ts +++ b/packages/lib/jobs/definitions/emails/send-team-deleted-email.ts @@ -1,4 +1,3 @@ -import { DocumentVisibility } from '@prisma/client'; import { z } from 'zod'; import type { JobDefinition } from '../../client/_internal/job'; @@ -9,23 +8,24 @@ const SEND_TEAM_DELETED_EMAIL_JOB_DEFINITION_SCHEMA = z.object({ team: z.object({ name: z.string(), url: z.string(), - teamGlobalSettings: z - .object({ - documentVisibility: z.nativeEnum(DocumentVisibility), - documentLanguage: z.string(), - includeSenderDetails: z.boolean(), - includeSigningCertificate: z.boolean(), - brandingEnabled: z.boolean(), - brandingLogo: z.string(), - brandingUrl: z.string(), - brandingCompanyDetails: z.string(), - brandingHidePoweredBy: z.boolean(), - teamId: z.number(), - typedSignatureEnabled: z.boolean(), - uploadSignatureEnabled: z.boolean(), - drawSignatureEnabled: z.boolean(), - }) - .nullish(), + // This is never passed along for some reason so commenting it out. + // teamGlobalSettings: z + // .object({ + // documentVisibility: z.nativeEnum(DocumentVisibility), + // documentLanguage: z.string(), + // includeSenderDetails: z.boolean(), + // includeSigningCertificate: z.boolean(), + // brandingEnabled: z.boolean(), + // brandingLogo: z.string(), + // brandingUrl: z.string(), + // brandingCompanyDetails: z.string(), + // brandingHidePoweredBy: z.boolean(), + // teamId: z.number(), + // typedSignatureEnabled: z.boolean(), + // uploadSignatureEnabled: z.boolean(), + // drawSignatureEnabled: z.boolean(), + // }) + // .nullish(), }), members: z.array( z.object({ diff --git a/packages/lib/jobs/definitions/emails/send-team-member-joined-email.ts b/packages/lib/jobs/definitions/emails/send-team-member-joined-email.ts deleted file mode 100644 index 258e7a34e..000000000 --- a/packages/lib/jobs/definitions/emails/send-team-member-joined-email.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { z } from 'zod'; - -import type { JobDefinition } from '../../client/_internal/job'; - -const SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_ID = 'send.team-member-joined.email'; - -const SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_SCHEMA = z.object({ - teamId: z.number(), - memberId: z.number(), -}); - -export type TSendTeamMemberJoinedEmailJobDefinition = z.infer< - typeof SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_SCHEMA ->; - -export const SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION = { - id: SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_ID, - name: 'Send Team Member Joined Email', - version: '1.0.0', - trigger: { - name: SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_ID, - schema: SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_SCHEMA, - }, - handler: async ({ payload, io }) => { - const handler = await import('./send-team-member-joined-email.handler'); - - await handler.run({ payload, io }); - }, -} as const satisfies JobDefinition< - typeof SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_ID, - TSendTeamMemberJoinedEmailJobDefinition ->; diff --git a/packages/lib/jobs/definitions/emails/send-team-member-left-email.handler.ts b/packages/lib/jobs/definitions/emails/send-team-member-left-email.handler.ts deleted file mode 100644 index 5e9ef402f..000000000 --- a/packages/lib/jobs/definitions/emails/send-team-member-left-email.handler.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { createElement } from 'react'; - -import { msg } from '@lingui/core/macro'; -import { TeamMemberRole } from '@prisma/client'; - -import { mailer } from '@documenso/email/mailer'; -import TeamJoinEmailTemplate from '@documenso/email/templates/team-join'; -import { prisma } from '@documenso/prisma'; - -import { getI18nInstance } from '../../../client-only/providers/i18n-server'; -import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app'; -import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email'; -import { getTeamSettings } from '../../../server-only/team/get-team-settings'; -import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n'; -import { teamGlobalSettingsToBranding } from '../../../utils/team-global-settings-to-branding'; -import type { JobRunIO } from '../../client/_internal/job'; -import type { TSendTeamMemberLeftEmailJobDefinition } from './send-team-member-left-email'; - -export const run = async ({ - payload, - io, -}: { - payload: TSendTeamMemberLeftEmailJobDefinition; - io: JobRunIO; -}) => { - const team = await prisma.team.findFirstOrThrow({ - where: { - id: payload.teamId, - }, - include: { - members: { - where: { - role: { - in: [TeamMemberRole.ADMIN, TeamMemberRole.MANAGER], - }, - }, - include: { - user: true, - }, - }, - }, - }); - - const settings = await getTeamSettings({ - teamId: payload.teamId, - }); - - const oldMember = await prisma.user.findFirstOrThrow({ - where: { - id: payload.memberUserId, - }, - }); - - for (const member of team.members) { - await io.runTask(`send-team-member-left-email--${oldMember.id}_${member.id}`, async () => { - const emailContent = createElement(TeamJoinEmailTemplate, { - assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(), - baseUrl: NEXT_PUBLIC_WEBAPP_URL(), - memberName: oldMember.name || '', - memberEmail: oldMember.email, - teamName: team.name, - teamUrl: team.url, - }); - - const branding = teamGlobalSettingsToBranding(settings, team.id); - const lang = settings.documentLanguage; - - const [html, text] = await Promise.all([ - renderEmailWithI18N(emailContent, { - lang, - branding, - }), - renderEmailWithI18N(emailContent, { - lang, - branding, - plainText: true, - }), - ]); - - const i18n = await getI18nInstance(lang); - - await mailer.sendMail({ - to: member.user.email, - from: { - name: FROM_NAME, - address: FROM_ADDRESS, - }, - subject: i18n._(msg`A team member has left ${team.name}`), - html, - text, - }); - }); - } -}; diff --git a/packages/lib/jobs/definitions/emails/send-team-member-left-email.ts b/packages/lib/jobs/definitions/emails/send-team-member-left-email.ts deleted file mode 100644 index f940896a4..000000000 --- a/packages/lib/jobs/definitions/emails/send-team-member-left-email.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { z } from 'zod'; - -import type { JobDefinition } from '../../client/_internal/job'; - -const SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_ID = 'send.team-member-left.email'; - -const SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_SCHEMA = z.object({ - teamId: z.number(), - memberUserId: z.number(), -}); - -export type TSendTeamMemberLeftEmailJobDefinition = z.infer< - typeof SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_SCHEMA ->; - -export const SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION = { - id: SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_ID, - name: 'Send Team Member Left Email', - version: '1.0.0', - trigger: { - name: SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_ID, - schema: SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_SCHEMA, - }, - handler: async ({ payload, io }) => { - const handler = await import('./send-team-member-left-email.handler'); - - await handler.run({ payload, io }); - }, -} as const satisfies JobDefinition< - typeof SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_ID, - TSendTeamMemberLeftEmailJobDefinition ->; diff --git a/packages/lib/server-only/admin/get-all-subscriptions.ts b/packages/lib/server-only/admin/get-all-subscriptions.ts deleted file mode 100644 index 5080c4c22..000000000 --- a/packages/lib/server-only/admin/get-all-subscriptions.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { prisma } from '@documenso/prisma'; - -export const findSubscriptions = async () => { - return prisma.subscription.findMany({ - select: { - id: true, - status: true, - createdAt: true, - periodEnd: true, - userId: true, - }, - }); -}; diff --git a/packages/lib/server-only/admin/get-signing-volume.ts b/packages/lib/server-only/admin/get-signing-volume.ts index dbaa6307a..8ed734ecc 100644 --- a/packages/lib/server-only/admin/get-signing-volume.ts +++ b/packages/lib/server-only/admin/get-signing-volume.ts @@ -29,37 +29,26 @@ export async function getSigningVolume({ let findQuery = kyselyPrisma.$kysely .selectFrom('Subscription as s') - .leftJoin('User as u', 's.userId', 'u.id') - .leftJoin('Team as t', 's.teamId', 't.id') - .leftJoin('Document as ud', (join) => + .innerJoin('Organisation as o', 's.organisationId', 'o.id') + .leftJoin('Team as t', 'o.id', 't.organisationId') + .leftJoin('Document as d', (join) => join - .onRef('u.id', '=', 'ud.userId') - .on('ud.status', '=', sql.lit(DocumentStatus.COMPLETED)) - .on('ud.deletedAt', 'is', null) - .on('ud.teamId', 'is', null), - ) - .leftJoin('Document as td', (join) => - join - .onRef('t.id', '=', 'td.teamId') - .on('td.status', '=', sql.lit(DocumentStatus.COMPLETED)) - .on('td.deletedAt', 'is', null), + .onRef('t.id', '=', 'd.teamId') + .on('d.status', '=', sql.lit(DocumentStatus.COMPLETED)) + .on('d.deletedAt', 'is', null), ) .where(sql`s.status = ${SubscriptionStatus.ACTIVE}::"SubscriptionStatus"`) .where((eb) => - eb.or([ - eb('u.name', 'ilike', `%${search}%`), - eb('u.email', 'ilike', `%${search}%`), - eb('t.name', 'ilike', `%${search}%`), - ]), + eb.or([eb('o.name', 'ilike', `%${search}%`), eb('t.name', 'ilike', `%${search}%`)]), ) .select([ 's.id as id', 's.createdAt as createdAt', 's.planId as planId', - sql`COALESCE(u.name, t.name, u.email, 'Unknown')`.as('name'), - sql`COUNT(DISTINCT ud.id) + COUNT(DISTINCT td.id)`.as('signingVolume'), + sql`COALESCE(o.name, 'Unknown')`.as('name'), + sql`COUNT(DISTINCT d.id)`.as('signingVolume'), ]) - .groupBy(['s.id', 'u.name', 't.name', 'u.email']); + .groupBy(['s.id', 'o.name']); switch (sortBy) { case 'name': @@ -79,15 +68,11 @@ export async function getSigningVolume({ const countQuery = kyselyPrisma.$kysely .selectFrom('Subscription as s') - .leftJoin('User as u', 's.userId', 'u.id') - .leftJoin('Team as t', 's.teamId', 't.id') + .innerJoin('Organisation as o', 's.organisationId', 'o.id') + .leftJoin('Team as t', 'o.id', 't.organisationId') .where(sql`s.status = ${SubscriptionStatus.ACTIVE}::"SubscriptionStatus"`) .where((eb) => - eb.or([ - eb('u.name', 'ilike', `%${search}%`), - eb('u.email', 'ilike', `%${search}%`), - eb('t.name', 'ilike', `%${search}%`), - ]), + eb.or([eb('o.name', 'ilike', `%${search}%`), eb('t.name', 'ilike', `%${search}%`)]), ) .select(({ fn }) => [fn.countAll().as('count')]); diff --git a/packages/lib/server-only/admin/get-users-stats.ts b/packages/lib/server-only/admin/get-users-stats.ts index f44a1332e..229433803 100644 --- a/packages/lib/server-only/admin/get-users-stats.ts +++ b/packages/lib/server-only/admin/get-users-stats.ts @@ -7,13 +7,11 @@ export const getUsersCount = async () => { return await prisma.user.count(); }; -export const getUsersWithSubscriptionsCount = async () => { - return await prisma.user.count({ +export const getOrganisationsWithSubscriptionsCount = async () => { + return await prisma.organisation.count({ where: { - subscriptions: { - some: { - status: SubscriptionStatus.ACTIVE, - }, + subscription: { + status: SubscriptionStatus.ACTIVE, }, }, }); diff --git a/packages/lib/server-only/document/create-document-v2.ts b/packages/lib/server-only/document/create-document-v2.ts index ed1e0e16e..fa3f655c0 100644 --- a/packages/lib/server-only/document/create-document-v2.ts +++ b/packages/lib/server-only/document/create-document-v2.ts @@ -7,7 +7,6 @@ import { WebhookTriggerEvents, } from '@prisma/client'; -import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import { normalizePdf as makeNormalizedPdf } from '@documenso/lib/server-only/pdf/normalize-pdf'; import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs'; @@ -27,6 +26,7 @@ import { getFileServerSide } from '../../universal/upload/get-file.server'; import { putPdfFileServerSide } from '../../universal/upload/put-file.server'; import { createDocumentAuthOptions, createRecipientAuthOptions } from '../../utils/document-auth'; import { determineDocumentVisibility } from '../../utils/document-visibility'; +import { buildTeamWhereQuery } from '../../utils/teams'; import { getMemberRoles } from '../team/get-member-roles'; import { getTeamSettings } from '../team/get-team-settings'; import { triggerWebhook } from '../webhooks/trigger/trigger-webhook'; @@ -60,6 +60,23 @@ export const createDocumentV2 = async ({ }: CreateDocumentOptions) => { const { title, formValues } = data; + const team = await prisma.team.findFirst({ + where: buildTeamWhereQuery(userId, teamId), + include: { + organisation: { + select: { + organisationClaim: true, + }, + }, + }, + }); + + if (!team) { + throw new AppError(AppErrorCode.NOT_FOUND, { + message: 'Team not found', + }); + } + const settings = await getTeamSettings({ userId, teamId, @@ -96,17 +113,13 @@ export const createDocumentV2 = async ({ const recipientsHaveActionAuth = data.recipients?.some((recipient) => recipient.actionAuth); // Check if user has permission to set the global action auth. - if (authOptions.globalActionAuth || recipientsHaveActionAuth) { - const isDocumentEnterprise = await isUserEnterprise({ - userId, - teamId, + if ( + (authOptions.globalActionAuth || recipientsHaveActionAuth) && + !team.organisation.organisationClaim.flags.cfr21 + ) { + throw new AppError(AppErrorCode.UNAUTHORIZED, { + message: 'You do not have permission to set the action auth', }); - - if (!isDocumentEnterprise) { - throw new AppError(AppErrorCode.UNAUTHORIZED, { - message: 'You do not have permission to set the action auth', - }); - } } const { teamRole } = await getMemberRoles({ diff --git a/packages/lib/server-only/document/update-document.ts b/packages/lib/server-only/document/update-document.ts index 73523d190..b067469b8 100644 --- a/packages/lib/server-only/document/update-document.ts +++ b/packages/lib/server-only/document/update-document.ts @@ -2,7 +2,6 @@ import { DocumentVisibility } from '@prisma/client'; import { DocumentStatus, TeamMemberRole } from '@prisma/client'; import { match } from 'ts-pattern'; -import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs'; import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata'; import type { CreateDocumentAuditLogDataResponse } from '@documenso/lib/utils/document-audit-logs'; @@ -43,6 +42,17 @@ export const updateDocument = async ({ const document = await prisma.document.findFirst({ where: documentWhereInput, + include: { + team: { + select: { + organisation: { + select: { + organisationClaim: true, + }, + }, + }, + }, + }, }); if (!document) { @@ -108,17 +118,10 @@ export const updateDocument = async ({ data?.globalActionAuth === undefined ? documentGlobalActionAuth : data.globalActionAuth; // Check if user has permission to set the global action auth. - if (newGlobalActionAuth) { - const isDocumentEnterprise = await isUserEnterprise({ - userId, - teamId, + if (newGlobalActionAuth && !document.team.organisation.organisationClaim.flags.cfr21) { + throw new AppError(AppErrorCode.UNAUTHORIZED, { + message: 'You do not have permission to set the action auth', }); - - if (!isDocumentEnterprise) { - throw new AppError(AppErrorCode.UNAUTHORIZED, { - message: 'You do not have permission to set the action auth', - }); - } } const isTitleSame = data.title === undefined || data.title === document.title; diff --git a/packages/lib/server-only/organisation/accept-organisation-invitation.ts b/packages/lib/server-only/organisation/accept-organisation-invitation.ts index 3c9049a4a..778fe3f63 100644 --- a/packages/lib/server-only/organisation/accept-organisation-invitation.ts +++ b/packages/lib/server-only/organisation/accept-organisation-invitation.ts @@ -3,6 +3,7 @@ import { OrganisationGroupType, OrganisationMemberInviteStatus } from '@prisma/c import { prisma } from '@documenso/prisma'; import { AppError, AppErrorCode } from '../../errors/app-error'; +import { jobs } from '../../jobs/client'; export type AcceptOrganisationInvitationOptions = { token: string; @@ -21,7 +22,8 @@ export const acceptOrganisationInvitation = async ({ include: { organisation: { include: { - subscriptions: true, + subscription: true, + organisationClaim: true, groups: { include: { teamGroups: true, @@ -46,19 +48,10 @@ export const acceptOrganisationInvitation = async ({ }, }); - // If no user exists for the invitation, accept the invitation and create the organisation - // user when the user signs up. if (!user) { - await prisma.organisationMemberInvite.update({ - where: { - id: organisationMemberInvite.id, - }, - data: { - status: OrganisationMemberInviteStatus.ACCEPTED, - }, + throw new AppError(AppErrorCode.NOT_FOUND, { + message: 'User must exist to accept an organisation invitation', }); - - return; } const { organisation } = organisationMemberInvite; @@ -98,28 +91,13 @@ export const acceptOrganisationInvitation = async ({ }, }); - // Todo: Orgs - // if (IS_BILLING_ENABLED() && team.subscription) { - // const numberOfSeats = await tx.teamMember.count({ - // where: { - // teamId: organisationMemberInvite.teamId, - // }, - // }); - - // await updateSubscriptionItemQuantity({ - // priceId: team.subscription.priceId, - // subscriptionId: team.subscription.planId, - // quantity: numberOfSeats, - // }); - // } - - // await jobs.triggerJob({ - // name: 'send.team-member-joined.email', - // payload: { - // teamId: teamMember.teamId, - // memberId: teamMember.id, - // }, - // }); + await jobs.triggerJob({ + name: 'send.organisation-member-joined.email', + payload: { + organisationId: organisation.id, + memberUserId: user.id, + }, + }); }, { timeout: 30_000 }, ); diff --git a/packages/lib/server-only/organisation/create-organisation-member-invites.ts b/packages/lib/server-only/organisation/create-organisation-member-invites.ts index 64675a6ae..b9df5b815 100644 --- a/packages/lib/server-only/organisation/create-organisation-member-invites.ts +++ b/packages/lib/server-only/organisation/create-organisation-member-invites.ts @@ -5,6 +5,7 @@ import type { Organisation, OrganisationGlobalSettings, Prisma } from '@prisma/c import { OrganisationMemberInviteStatus } from '@prisma/client'; import { nanoid } from 'nanoid'; +import { syncMemberCountWithStripeSeatPlan } from '@documenso/ee/server-only/stripe/update-subscription-item-quantity'; import { mailer } from '@documenso/email/mailer'; import { OrganisationInviteEmailTemplate } from '@documenso/email/templates/organisation-invite'; import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; @@ -16,12 +17,11 @@ import { prisma } from '@documenso/prisma'; import type { TCreateOrganisationMemberInvitesRequestSchema } from '@documenso/trpc/server/organisation-router/create-organisation-member-invites.types'; import { getI18nInstance } from '../../client-only/providers/i18n-server'; -import { - buildOrganisationWhereQuery, - getHighestOrganisationRoleInGroup, -} from '../../utils/organisations'; +import { validateIfSubscriptionIsRequired } from '../../utils/billing'; +import { buildOrganisationWhereQuery } from '../../utils/organisations'; import { renderEmailWithI18N } from '../../utils/render-email-with-i18n'; import { organisationGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding'; +import { getMemberOrganisationRole } from '../team/get-member-roles'; export type CreateOrganisationMemberInvitesOptions = { userId: number; @@ -56,8 +56,14 @@ export const createOrganisationMemberInvites = async ({ }, }, }, - invites: true, + invites: { + where: { + status: OrganisationMemberInviteStatus.PENDING, + }, + }, organisationGlobalSettings: true, + organisationClaim: true, + subscription: true, }, }); @@ -65,38 +71,20 @@ export const createOrganisationMemberInvites = async ({ throw new AppError(AppErrorCode.NOT_FOUND); } - const currentOrganisationMember = await prisma.organisationMember.findFirst({ - where: { - userId, - organisationId, - }, - include: { - organisationGroupMembers: { - include: { - group: true, - }, - }, + const { organisationClaim } = organisation; + + const subscription = validateIfSubscriptionIsRequired(organisation.subscription); + + const currentOrganisationMemberRole = await getMemberOrganisationRole({ + organisationId: organisation.id, + reference: { + type: 'User', + id: userId, }, }); - if (!currentOrganisationMember) { - throw new AppError(AppErrorCode.UNAUTHORIZED); - } - - const currentOrganisationMemberRole = getHighestOrganisationRoleInGroup( - currentOrganisationMember.organisationGroupMembers.map((member) => member.group), - ); - const organisationMemberEmails = organisation.members.map((member) => member.user.email); - const organisationMemberInviteEmails = organisation.invites - .filter((invite) => invite.status === OrganisationMemberInviteStatus.PENDING) - .map((invite) => invite.email); - - if (!currentOrganisationMember) { - throw new AppError(AppErrorCode.UNAUTHORIZED, { - message: 'User not part of organisation.', - }); - } + const organisationMemberInviteEmails = organisation.invites.map((invite) => invite.email); const usersToInvite = invitations.filter((invitation) => { // Filter out users that are already members of the organisation. @@ -123,7 +111,6 @@ export const createOrganisationMemberInvites = async ({ }); } - // Todo: (orgs) const organisationMemberInvites: Prisma.OrganisationMemberInviteCreateManyInput[] = usersToInvite.map(({ email, organisationRole }) => ({ email, @@ -132,9 +119,21 @@ export const createOrganisationMemberInvites = async ({ token: nanoid(32), })); - console.log({ - organisationMemberInvites, - }); + const numberOfCurrentMembers = organisation.members.length; + const numberOfCurrentInvites = organisation.invites.length; + const numberOfNewInvites = organisationMemberInvites.length; + + const totalMemberCountWithInvites = + numberOfCurrentMembers + numberOfCurrentInvites + numberOfNewInvites; + + // Handle billing for seat based plans. + if (subscription) { + await syncMemberCountWithStripeSeatPlan( + subscription, + organisationClaim, + totalMemberCountWithInvites, + ); + } await prisma.organisationMemberInvite.createMany({ data: organisationMemberInvites, diff --git a/packages/lib/server-only/organisation/create-organisation.ts b/packages/lib/server-only/organisation/create-organisation.ts index ac8e58c05..51e99528f 100644 --- a/packages/lib/server-only/organisation/create-organisation.ts +++ b/packages/lib/server-only/organisation/create-organisation.ts @@ -5,32 +5,45 @@ import { prisma } from '@documenso/prisma'; import { ORGANISATION_INTERNAL_GROUPS } from '../../constants/organisations'; import { AppErrorCode } from '../../errors/app-error'; import { AppError } from '../../errors/app-error'; -import { alphaid } from '../../universal/id'; +import { alphaid, generatePrefixedId } from '../../universal/id'; import { generateDefaultOrganisationSettings } from '../../utils/organisations'; +import { generateDefaultOrganisationClaims } from '../../utils/organisations-claims'; import { createTeam } from '../team/create-team'; type CreateOrganisationOptions = { userId: number; name: string; - url: string; + url?: string; + customerId?: string; }; -export const createOrganisation = async ({ name, url, userId }: CreateOrganisationOptions) => { +export const createOrganisation = async ({ + name, + url, + userId, + customerId, +}: CreateOrganisationOptions) => { return await prisma.$transaction(async (tx) => { const organisationSetting = await tx.organisationGlobalSettings.create({ data: generateDefaultOrganisationSettings(), }); + const organisationClaim = await tx.organisationClaim.create({ + data: generateDefaultOrganisationClaims(), + }); + const organisation = await tx.organisation .create({ data: { name, - url, // Todo: orgs constraint this + url: url || generatePrefixedId('org'), ownerUserId: userId, organisationGlobalSettingsId: organisationSetting.id, + organisationClaimId: organisationClaim.id, groups: { create: ORGANISATION_INTERNAL_GROUPS, }, + customerId, }, include: { groups: true, @@ -86,7 +99,7 @@ export const createPersonalOrganisation = async ({ const organisation = await createOrganisation({ name: 'Personal Organisation', userId, - url: orgUrl || `org_${alphaid(8)}`, + url: orgUrl, }).catch((err) => { console.error(err); @@ -94,7 +107,7 @@ export const createPersonalOrganisation = async ({ throw err; } - // Todo: (orgs) Add logging. + // Todo: (LOGS) }); if (organisation) { @@ -106,7 +119,8 @@ export const createPersonalOrganisation = async ({ inheritMembers: true, }).catch((err) => { console.error(err); - // Todo: (orgs) Add logging. + + // Todo: (LOGS) }); } diff --git a/packages/lib/server-only/organisation/get-organisation-claims.ts b/packages/lib/server-only/organisation/get-organisation-claims.ts new file mode 100644 index 000000000..18096269d --- /dev/null +++ b/packages/lib/server-only/organisation/get-organisation-claims.ts @@ -0,0 +1,39 @@ +import { prisma } from '@documenso/prisma'; + +import { AppError, AppErrorCode } from '../../errors/app-error'; + +export const getOrganisationClaim = async ({ organisationId }: { organisationId: string }) => { + const organisationClaim = await prisma.organisationClaim.findFirst({ + where: { + organisation: { + id: organisationId, + }, + }, + }); + + if (!organisationClaim) { + throw new AppError(AppErrorCode.NOT_FOUND); + } + + return organisationClaim; +}; + +export const getOrganisationClaimByTeamId = async ({ teamId }: { teamId: number }) => { + const organisationClaim = await prisma.organisationClaim.findFirst({ + where: { + organisation: { + teams: { + some: { + id: teamId, + }, + }, + }, + }, + }); + + if (!organisationClaim) { + throw new AppError(AppErrorCode.NOT_FOUND); + } + + return organisationClaim; +}; diff --git a/packages/lib/server-only/public-api/create-api-token.ts b/packages/lib/server-only/public-api/create-api-token.ts index 73c932ce9..0423b13fa 100644 --- a/packages/lib/server-only/public-api/create-api-token.ts +++ b/packages/lib/server-only/public-api/create-api-token.ts @@ -1,13 +1,14 @@ -import { TeamMemberRole } from '@prisma/client'; import type { Duration } from 'luxon'; import { DateTime } from 'luxon'; import { prisma } from '@documenso/prisma'; +import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '../../constants/teams'; // temporary choice for testing only import * as timeConstants from '../../constants/time'; import { AppError, AppErrorCode } from '../../errors/app-error'; import { alphaid } from '../../universal/id'; +import { buildTeamWhereQuery } from '../../utils/teams'; import { hashString } from '../auth/hash'; type TimeConstants = typeof timeConstants & { @@ -33,20 +34,14 @@ export const createApiToken = async ({ const timeConstantsRecords: TimeConstants = timeConstants; - if (teamId) { - const member = await prisma.teamMember.findFirst({ - where: { - userId, - teamId, - role: TeamMemberRole.ADMIN, - }, - }); + const team = await prisma.team.findFirst({ + where: buildTeamWhereQuery(teamId, userId, TEAM_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_TEAM']), + }); - if (!member) { - throw new AppError(AppErrorCode.UNAUTHORIZED, { - message: 'You do not have permission to create a token for this team', - }); - } + if (!team) { + throw new AppError(AppErrorCode.UNAUTHORIZED, { + message: 'You do not have permission to create a token for this team', + }); } const storedToken = await prisma.apiToken.create({ diff --git a/packages/lib/server-only/public-api/delete-api-token-by-id.ts b/packages/lib/server-only/public-api/delete-api-token-by-id.ts index 87097971d..0321ca5fc 100644 --- a/packages/lib/server-only/public-api/delete-api-token-by-id.ts +++ b/packages/lib/server-only/public-api/delete-api-token-by-id.ts @@ -1,7 +1,9 @@ -import { TeamMemberRole } from '@prisma/client'; - import { prisma } from '@documenso/prisma'; +import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '../../constants/teams'; +import { AppError, AppErrorCode } from '../../errors/app-error'; +import { buildTeamWhereQuery } from '../../utils/teams'; + export type DeleteTokenByIdOptions = { id: number; userId: number; @@ -9,24 +11,20 @@ export type DeleteTokenByIdOptions = { }; export const deleteTokenById = async ({ id, userId, teamId }: DeleteTokenByIdOptions) => { - if (teamId) { - const member = await prisma.teamMember.findFirst({ - where: { - userId, - teamId, - role: TeamMemberRole.ADMIN, - }, - }); + const team = await prisma.team.findFirst({ + where: buildTeamWhereQuery(teamId, userId, TEAM_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_TEAM']), + }); - if (!member) { - throw new Error('You do not have permission to delete this token'); - } + if (!team) { + throw new AppError(AppErrorCode.UNAUTHORIZED, { + message: 'You do not have permission to delete this token', + }); } return await prisma.apiToken.delete({ where: { id, - teamId: teamId ?? null, + teamId, }, }); }; diff --git a/packages/lib/server-only/recipient/create-document-recipients.ts b/packages/lib/server-only/recipient/create-document-recipients.ts index 09c9bd0f1..26fa0494e 100644 --- a/packages/lib/server-only/recipient/create-document-recipients.ts +++ b/packages/lib/server-only/recipient/create-document-recipients.ts @@ -1,7 +1,6 @@ import { RecipientRole } from '@prisma/client'; import { SendStatus, SigningStatus } from '@prisma/client'; -import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs'; import type { TRecipientAccessAuthTypes } from '@documenso/lib/types/document-auth'; import { type TRecipientActionAuthTypes } from '@documenso/lib/types/document-auth'; @@ -46,6 +45,15 @@ export const createDocumentRecipients = async ({ where: documentWhereInput, include: { recipients: true, + team: { + select: { + organisation: { + select: { + organisationClaim: true, + }, + }, + }, + }, }, }); @@ -64,17 +72,10 @@ export const createDocumentRecipients = async ({ const recipientsHaveActionAuth = recipientsToCreate.some((recipient) => recipient.actionAuth); // Check if user has permission to set the global action auth. - if (recipientsHaveActionAuth) { - const isEnterprise = await isUserEnterprise({ - userId, - teamId, + if (recipientsHaveActionAuth && !document.team.organisation.organisationClaim.flags.cfr21) { + throw new AppError(AppErrorCode.UNAUTHORIZED, { + message: 'You do not have permission to set the action auth', }); - - if (!isEnterprise) { - throw new AppError(AppErrorCode.UNAUTHORIZED, { - message: 'You do not have permission to set the action auth', - }); - } } const normalizedRecipients = recipientsToCreate.map((recipient) => ({ diff --git a/packages/lib/server-only/recipient/create-template-recipients.ts b/packages/lib/server-only/recipient/create-template-recipients.ts index c992442a5..bd2c31dc8 100644 --- a/packages/lib/server-only/recipient/create-template-recipients.ts +++ b/packages/lib/server-only/recipient/create-template-recipients.ts @@ -1,7 +1,6 @@ import { RecipientRole } from '@prisma/client'; import { SendStatus, SigningStatus } from '@prisma/client'; -import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; import type { TRecipientAccessAuthTypes } from '@documenso/lib/types/document-auth'; import { type TRecipientActionAuthTypes } from '@documenso/lib/types/document-auth'; import { nanoid } from '@documenso/lib/universal/id'; @@ -38,6 +37,15 @@ export const createTemplateRecipients = async ({ }, include: { recipients: true, + team: { + select: { + organisation: { + select: { + organisationClaim: true, + }, + }, + }, + }, }, }); @@ -50,17 +58,10 @@ export const createTemplateRecipients = async ({ const recipientsHaveActionAuth = recipientsToCreate.some((recipient) => recipient.actionAuth); // Check if user has permission to set the global action auth. - if (recipientsHaveActionAuth) { - const isEnterprise = await isUserEnterprise({ - userId, - teamId, + if (recipientsHaveActionAuth && !template.team.organisation.organisationClaim.flags.cfr21) { + throw new AppError(AppErrorCode.UNAUTHORIZED, { + message: 'You do not have permission to set the action auth', }); - - if (!isEnterprise) { - throw new AppError(AppErrorCode.UNAUTHORIZED, { - message: 'You do not have permission to set the action auth', - }); - } } const normalizedRecipients = recipientsToCreate.map((recipient) => ({ diff --git a/packages/lib/server-only/recipient/set-document-recipients.ts b/packages/lib/server-only/recipient/set-document-recipients.ts index 913d74fc5..5a2d2d129 100644 --- a/packages/lib/server-only/recipient/set-document-recipients.ts +++ b/packages/lib/server-only/recipient/set-document-recipients.ts @@ -5,7 +5,6 @@ import type { Recipient } from '@prisma/client'; import { RecipientRole } from '@prisma/client'; import { SendStatus, SigningStatus } from '@prisma/client'; -import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; import { mailer } from '@documenso/email/mailer'; import RecipientRemovedFromDocumentTemplate from '@documenso/email/templates/recipient-removed-from-document'; import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs'; @@ -60,6 +59,15 @@ export const setDocumentRecipients = async ({ include: { fields: true, documentMeta: true, + team: { + select: { + organisation: { + select: { + organisationClaim: true, + }, + }, + }, + }, }, }); @@ -90,17 +98,10 @@ export const setDocumentRecipients = async ({ const recipientsHaveActionAuth = recipients.some((recipient) => recipient.actionAuth); // Check if user has permission to set the global action auth. - if (recipientsHaveActionAuth) { - const isDocumentEnterprise = await isUserEnterprise({ - userId, - teamId, + if (recipientsHaveActionAuth && !document.team.organisation.organisationClaim.flags.cfr21) { + throw new AppError(AppErrorCode.UNAUTHORIZED, { + message: 'You do not have permission to set the action auth', }); - - if (!isDocumentEnterprise) { - throw new AppError(AppErrorCode.UNAUTHORIZED, { - message: 'You do not have permission to set the action auth', - }); - } } const normalizedRecipients = recipients.map((recipient) => ({ diff --git a/packages/lib/server-only/recipient/set-template-recipients.ts b/packages/lib/server-only/recipient/set-template-recipients.ts index f786e7ce0..832c3a447 100644 --- a/packages/lib/server-only/recipient/set-template-recipients.ts +++ b/packages/lib/server-only/recipient/set-template-recipients.ts @@ -1,7 +1,6 @@ import type { Recipient } from '@prisma/client'; import { RecipientRole } from '@prisma/client'; -import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; import { DIRECT_TEMPLATE_RECIPIENT_EMAIL, DIRECT_TEMPLATE_RECIPIENT_NAME, @@ -44,6 +43,15 @@ export const setTemplateRecipients = async ({ }, include: { directLink: true, + team: { + select: { + organisation: { + select: { + organisationClaim: true, + }, + }, + }, + }, }, }); @@ -54,17 +62,10 @@ export const setTemplateRecipients = async ({ const recipientsHaveActionAuth = recipients.some((recipient) => recipient.actionAuth); // Check if user has permission to set the global action auth. - if (recipientsHaveActionAuth) { - const isDocumentEnterprise = await isUserEnterprise({ - userId, - teamId, + if (recipientsHaveActionAuth && !template.team.organisation.organisationClaim.flags.cfr21) { + throw new AppError(AppErrorCode.UNAUTHORIZED, { + message: 'You do not have permission to set the action auth', }); - - if (!isDocumentEnterprise) { - throw new AppError(AppErrorCode.UNAUTHORIZED, { - message: 'You do not have permission to set the action auth', - }); - } } const normalizedRecipients = recipients.map((recipient) => { diff --git a/packages/lib/server-only/recipient/update-document-recipients.ts b/packages/lib/server-only/recipient/update-document-recipients.ts index 1d23e7eaf..19cc558f5 100644 --- a/packages/lib/server-only/recipient/update-document-recipients.ts +++ b/packages/lib/server-only/recipient/update-document-recipients.ts @@ -2,7 +2,6 @@ import type { Recipient } from '@prisma/client'; import { RecipientRole } from '@prisma/client'; import { SendStatus, SigningStatus } from '@prisma/client'; -import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs'; import type { TRecipientAccessAuthTypes } from '@documenso/lib/types/document-auth'; import { @@ -47,6 +46,15 @@ export const updateDocumentRecipients = async ({ include: { fields: true, recipients: true, + team: { + select: { + organisation: { + select: { + organisationClaim: true, + }, + }, + }, + }, }, }); @@ -65,17 +73,10 @@ export const updateDocumentRecipients = async ({ const recipientsHaveActionAuth = recipients.some((recipient) => recipient.actionAuth); // Check if user has permission to set the global action auth. - if (recipientsHaveActionAuth) { - const isEnterprise = await isUserEnterprise({ - userId, - teamId, + if (recipientsHaveActionAuth && !document.team.organisation.organisationClaim.flags.cfr21) { + throw new AppError(AppErrorCode.UNAUTHORIZED, { + message: 'You do not have permission to set the action auth', }); - - if (!isEnterprise) { - throw new AppError(AppErrorCode.UNAUTHORIZED, { - message: 'You do not have permission to set the action auth', - }); - } } const recipientsToUpdate = recipients.map((recipient) => { diff --git a/packages/lib/server-only/recipient/update-recipient.ts b/packages/lib/server-only/recipient/update-recipient.ts index c3fe87afd..bce416f49 100644 --- a/packages/lib/server-only/recipient/update-recipient.ts +++ b/packages/lib/server-only/recipient/update-recipient.ts @@ -1,6 +1,5 @@ -import type { RecipientRole, Team } from '@prisma/client'; +import type { RecipientRole } from '@prisma/client'; -import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; import { prisma } from '@documenso/prisma'; import { AppError, AppErrorCode } from '../../errors/app-error'; @@ -12,6 +11,7 @@ import { import type { RequestMetadata } from '../../universal/extract-request-metadata'; import { createDocumentAuditLogData, diffRecipientChanges } from '../../utils/document-audit-logs'; import { createRecipientAuthOptions } from '../../utils/document-auth'; +import { buildTeamWhereQuery } from '../../utils/teams'; export type UpdateRecipientOptions = { documentId: number; @@ -44,57 +44,43 @@ export const updateRecipient = async ({ document: { id: documentId, userId, - team: { - id: teamId, - members: { - some: { - userId, + team: buildTeamWhereQuery(teamId, userId), // Todo: orgs i know i messed the orders of some of these up somewhere + }, + }, + include: { + document: { + include: { + team: { + include: { + organisation: { + select: { + organisationClaim: true, + }, + }, }, }, }, }, }, - include: { - document: true, - }, }); - let team: Team | null = null; - - if (teamId) { - team = await prisma.team.findFirst({ - where: { - id: teamId, - members: { - some: { - userId, - }, - }, - }, - }); - } - const user = await prisma.user.findFirstOrThrow({ where: { id: userId, }, }); - if (!recipient) { + // Todo: orgs check if this is supposed to only be documents + if (!recipient || !recipient.document) { throw new Error('Recipient not found'); } - if (actionAuth) { - const isDocumentEnterprise = await isUserEnterprise({ - userId, - teamId, - }); + const team = recipient.document.team; - if (!isDocumentEnterprise) { - throw new AppError(AppErrorCode.UNAUTHORIZED, { - message: 'You do not have permission to set the action auth', - }); - } + if (actionAuth && !recipient.document.team.organisation.organisationClaim.flags.cfr21) { + throw new AppError(AppErrorCode.UNAUTHORIZED, { + message: 'You do not have permission to set the action auth', + }); } const recipientAuthOptions = ZRecipientAuthOptionsSchema.parse(recipient.authOptions); diff --git a/packages/lib/server-only/recipient/update-template-recipients.ts b/packages/lib/server-only/recipient/update-template-recipients.ts index 23a115b55..864963d01 100644 --- a/packages/lib/server-only/recipient/update-template-recipients.ts +++ b/packages/lib/server-only/recipient/update-template-recipients.ts @@ -1,7 +1,6 @@ import { RecipientRole } from '@prisma/client'; import { SendStatus, SigningStatus } from '@prisma/client'; -import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; import type { TRecipientAccessAuthTypes } from '@documenso/lib/types/document-auth'; import { type TRecipientActionAuthTypes, @@ -41,6 +40,15 @@ export const updateTemplateRecipients = async ({ }, include: { recipients: true, + team: { + select: { + organisation: { + select: { + organisationClaim: true, + }, + }, + }, + }, }, }); @@ -53,17 +61,10 @@ export const updateTemplateRecipients = async ({ const recipientsHaveActionAuth = recipients.some((recipient) => recipient.actionAuth); // Check if user has permission to set the global action auth. - if (recipientsHaveActionAuth) { - const isEnterprise = await isUserEnterprise({ - userId, - teamId, + if (recipientsHaveActionAuth && !template.team.organisation.organisationClaim.flags.cfr21) { + throw new AppError(AppErrorCode.UNAUTHORIZED, { + message: 'You do not have permission to set the action auth', }); - - if (!isEnterprise) { - throw new AppError(AppErrorCode.UNAUTHORIZED, { - message: 'You do not have permission to set the action auth', - }); - } } const recipientsToUpdate = recipients.map((recipient) => { diff --git a/packages/lib/server-only/subscription/get-subscriptions-by-user-id.ts b/packages/lib/server-only/subscription/get-subscriptions-by-user-id.ts deleted file mode 100644 index 264a30472..000000000 --- a/packages/lib/server-only/subscription/get-subscriptions-by-user-id.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { prisma } from '@documenso/prisma'; - -export type GetSubscriptionsByUserIdOptions = { - userId: number; -}; - -export const getSubscriptionsByUserId = async ({ userId }: GetSubscriptionsByUserIdOptions) => { - return await prisma.subscription.findMany({ - where: { - userId, - }, - }); -}; diff --git a/packages/lib/server-only/team/create-team-checkout-session.ts b/packages/lib/server-only/team/create-team-checkout-session.ts deleted file mode 100644 index 706bea48e..000000000 --- a/packages/lib/server-only/team/create-team-checkout-session.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { getCheckoutSession } from '@documenso/ee/server-only/stripe/get-checkout-session'; -import { getTeamPrices } from '@documenso/ee/server-only/stripe/get-team-prices'; -import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; -import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; -import { prisma } from '@documenso/prisma'; - -export type CreateTeamPendingCheckoutSession = { - userId: number; - pendingTeamId: number; - interval: 'monthly' | 'yearly'; -}; - -export const createTeamPendingCheckoutSession = async ({ - userId, - pendingTeamId, - interval, -}: CreateTeamPendingCheckoutSession) => { - const teamPendingCreation = await prisma.teamPending.findFirstOrThrow({ - where: { - id: pendingTeamId, - ownerUserId: userId, - }, - include: { - owner: true, - }, - }); - - const prices = await getTeamPrices(); - const priceId = prices[interval].priceId; - - try { - const stripeCheckoutSession = await getCheckoutSession({ - customerId: teamPendingCreation.customerId, - priceId, - returnUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/settings/teams`, - subscriptionMetadata: { - pendingTeamId: pendingTeamId.toString(), - }, - }); - - if (!stripeCheckoutSession) { - throw new AppError(AppErrorCode.UNKNOWN_ERROR); - } - - return stripeCheckoutSession; - } catch (e) { - console.error(e); - - // Absorb all the errors incase Stripe throws something sensitive. - throw new AppError(AppErrorCode.UNKNOWN_ERROR, { - message: 'Something went wrong.', - }); - } -}; diff --git a/packages/lib/server-only/team/create-team.ts b/packages/lib/server-only/team/create-team.ts index b8f629f9b..034ce7b06 100644 --- a/packages/lib/server-only/team/create-team.ts +++ b/packages/lib/server-only/team/create-team.ts @@ -1,19 +1,7 @@ -import { - OrganisationGroupType, - OrganisationMemberRole, - Prisma, - TeamMemberRole, -} from '@prisma/client'; -import type Stripe from 'stripe'; +import { OrganisationGroupType, OrganisationMemberRole, TeamMemberRole } from '@prisma/client'; import { match } from 'ts-pattern'; -import { z } from 'zod'; -import { createOrganisationCustomer } from '@documenso/ee/server-only/stripe/create-team-customer'; -import { getTeamRelatedPrices } from '@documenso/ee/server-only/stripe/get-team-related-prices'; -import { mapStripeSubscriptionToPrismaUpsertAction } from '@documenso/ee/server-only/stripe/webhook/on-subscription-updated'; -import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; -import { subscriptionsContainsActivePlan } from '@documenso/lib/utils/billing'; import { prisma } from '@documenso/prisma'; import { @@ -23,7 +11,6 @@ import { import { TEAM_INTERNAL_GROUPS } from '../../constants/teams'; import { buildOrganisationWhereQuery } from '../../utils/organisations'; import { generateDefaultTeamSettings } from '../../utils/teams'; -import { stripe } from '../stripe'; export type CreateTeamOptions = { /** @@ -41,7 +28,7 @@ export type CreateTeamOptions = { * * Used as the URL path, example: https://documenso.com/t/{teamUrl}/settings */ - teamUrl: string; + teamUrl: string; // Todo: orgs make unique /** * ID of the organisation the team belongs to. @@ -62,28 +49,13 @@ export type CreateTeamOptions = { }[]; }; -export const ZCreateTeamResponseSchema = z.union([ - z.object({ - paymentRequired: z.literal(false), - }), - z.object({ - paymentRequired: z.literal(true), - pendingTeamId: z.number(), - }), -]); - -export type TCreateTeamResponse = z.infer; - -/** - * Create a team or pending team depending on the user's subscription or application's billing settings. - */ export const createTeam = async ({ userId, teamName, teamUrl, organisationId, inheritMembers, -}: CreateTeamOptions): Promise => { +}: CreateTeamOptions) => { const organisation = await prisma.organisation.findFirst({ where: buildOrganisationWhereQuery( organisationId, @@ -91,8 +63,9 @@ export const createTeam = async ({ ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'], ), include: { - groups: true, // Todo: (orgs) - subscriptions: true, + groups: true, + subscription: true, + organisationClaim: true, owner: { select: { id: true, @@ -109,6 +82,21 @@ export const createTeam = async ({ }); } + // Validate they have enough team slots. 0 means they can create unlimited teams. + if (organisation.organisationClaim.teamCount !== 0) { + const teamCount = await prisma.team.count({ + where: { + organisationId, + }, + }); + + if (teamCount >= organisation.organisationClaim.teamCount) { + throw new AppError(AppErrorCode.LIMIT_EXCEEDED, { + message: 'You have reached the maximum number of teams for your plan.', + }); + } + } + // Inherit internal organisation groups to the team. // Organisation Admins/Mangers get assigned as team admins, members get assigned as team members. const internalOrganisationGroups = organisation.groups @@ -141,254 +129,46 @@ export const createTeam = async ({ .exhaustive(), ); - console.log({ - internalOrganisationGroups, - }); - - if (Date.now() > 0) { - await prisma.$transaction(async (tx) => { - const teamSettings = await tx.teamGlobalSettings.create({ - data: generateDefaultTeamSettings(), - }); - - const team = await tx.team.create({ - data: { - name: teamName, - url: teamUrl, - organisationId, - teamGlobalSettingsId: teamSettings.id, - teamGroups: { - createMany: { - // Attach the internal organisation groups to the team. - data: internalOrganisationGroups, - }, - }, - }, - include: { - teamGroups: true, - }, - }); - - // Create the internal team groups. - await Promise.all( - TEAM_INTERNAL_GROUPS.map(async (teamGroup) => - tx.organisationGroup.create({ - data: { - type: teamGroup.type, - organisationRole: LOWEST_ORGANISATION_ROLE, - organisationId, - teamGroups: { - create: { - teamId: team.id, - teamRole: teamGroup.teamRole, - }, - }, - }, - }), - ), - ); - }); - - return { - paymentRequired: false, - }; - } - - if (Date.now() > 0) { - throw new Error('Todo: Orgs'); - } - - let isPaymentRequired = IS_BILLING_ENABLED(); - let customerId: string | null = null; - - if (IS_BILLING_ENABLED()) { - const teamRelatedPriceIds = await getTeamRelatedPrices().then((prices) => - prices.map((price) => price.id), - ); - - isPaymentRequired = !subscriptionsContainsActivePlan( - organisation.subscriptions, - teamRelatedPriceIds, // Todo: (orgs) - ); - - customerId = await createOrganisationCustomer({ - name: organisation.owner.name ?? teamName, - email: organisation.owner.email, - }).then((customer) => customer.id); - - await prisma.organisation.update({ - where: { - id: organisationId, - }, - data: { - customerId, - }, - }); - } - - try { - // Create the team directly if no payment is required. - if (!isPaymentRequired) { - await prisma.team.create({ - data: { - name: teamName, - url: teamUrl, - organisationId, - members: { - create: [ - { - userId, - role: TeamMemberRole.ADMIN, // Todo: (orgs) - }, - ], - }, - teamGlobalSettings: { - create: {}, - }, - }, - }); - - return { - paymentRequired: false, - }; - } - - // Create a pending team if payment is required. - const pendingTeam = await prisma.$transaction(async (tx) => { - const existingTeamWithUrl = await tx.team.findUnique({ - where: { - url: teamUrl, - }, - }); - - const existingUserProfileWithUrl = await tx.user.findUnique({ - where: { - url: teamUrl, - }, - select: { - id: true, - }, - }); - - if (existingUserProfileWithUrl) { - throw new AppError(AppErrorCode.ALREADY_EXISTS, { - message: 'URL already taken.', - }); - } - - if (existingTeamWithUrl) { - throw new AppError(AppErrorCode.ALREADY_EXISTS, { - message: 'Team URL already exists.', - }); - } - - if (!customerId) { - throw new AppError(AppErrorCode.UNKNOWN_ERROR, { - message: 'Missing customer ID for pending teams.', - }); - } - - return await tx.teamPending.create({ - data: { - name: teamName, - url: teamUrl, - ownerUserId: user.id, - customerId, - }, - }); - }); - - return { - paymentRequired: true, - pendingTeamId: pendingTeam.id, - }; - } catch (err) { - console.error(err); - - if (!(err instanceof Prisma.PrismaClientKnownRequestError)) { - throw err; - } - - const target = z.array(z.string()).safeParse(err.meta?.target); - - if (err.code === 'P2002' && target.success && target.data.includes('url')) { - throw new AppError(AppErrorCode.ALREADY_EXISTS, { - message: 'Team URL already exists.', - }); - } - - throw err; - } -}; - -export type CreateTeamFromPendingTeamOptions = { - pendingTeamId: number; - subscription: Stripe.Subscription; -}; - -export const createTeamFromPendingTeam = async ({ - pendingTeamId, - subscription, -}: CreateTeamFromPendingTeamOptions) => { - const createdTeam = await prisma.$transaction(async (tx) => { - const pendingTeam = await tx.teamPending.findUniqueOrThrow({ - where: { - id: pendingTeamId, - }, - }); - - await tx.teamPending.delete({ - where: { - id: pendingTeamId, - }, + await prisma.$transaction(async (tx) => { + const teamSettings = await tx.teamGlobalSettings.create({ + data: generateDefaultTeamSettings(), }); const team = await tx.team.create({ data: { - name: pendingTeam.name, - url: pendingTeam.url, - ownerUserId: pendingTeam.ownerUserId, - customerId: pendingTeam.customerId, - members: { - create: [ - { - userId: pendingTeam.ownerUserId, - role: TeamMemberRole.ADMIN, - }, - ], + name: teamName, + url: teamUrl, + organisationId, + teamGlobalSettingsId: teamSettings.id, + teamGroups: { + createMany: { + // Attach the internal organisation groups to the team. + data: internalOrganisationGroups, + }, }, }, - }); - - await tx.teamGlobalSettings.upsert({ - where: { - teamId: team.id, - }, - update: {}, - create: { - teamId: team.id, + include: { + teamGroups: true, }, }); - await tx.subscription.upsert( - mapStripeSubscriptionToPrismaUpsertAction(subscription, undefined, team.id), + // Create the internal team groups. + await Promise.all( + TEAM_INTERNAL_GROUPS.map(async (teamGroup) => + tx.organisationGroup.create({ + data: { + type: teamGroup.type, + organisationRole: LOWEST_ORGANISATION_ROLE, + organisationId, + teamGroups: { + create: { + teamId: team.id, + teamRole: teamGroup.teamRole, + }, + }, + }, + }), + ), ); - - return team; }); - - // Attach the team ID to the subscription metadata for sanity reasons. - await stripe.subscriptions - .update(subscription.id, { - metadata: { - teamId: createdTeam.id.toString(), - }, - }) - .catch((e) => { - console.error(e); - // Non-critical error, but we want to log it so we can rectify it. - // Todo: Teams - Alert us. - }); - - return createdTeam; }; diff --git a/packages/lib/server-only/team/delete-team-pending.ts b/packages/lib/server-only/team/delete-team-pending.ts deleted file mode 100644 index b339fd862..000000000 --- a/packages/lib/server-only/team/delete-team-pending.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { prisma } from '@documenso/prisma'; - -export type DeleteTeamPendingOptions = { - userId: number; - pendingTeamId: number; -}; - -export const deleteTeamPending = async ({ userId, pendingTeamId }: DeleteTeamPendingOptions) => { - await prisma.teamPending.delete({ - where: { - id: pendingTeamId, - ownerUserId: userId, - }, - }); -}; diff --git a/packages/lib/server-only/team/delete-team.ts b/packages/lib/server-only/team/delete-team.ts index aa6b337a3..f8bb5ea08 100644 --- a/packages/lib/server-only/team/delete-team.ts +++ b/packages/lib/server-only/team/delete-team.ts @@ -1,8 +1,8 @@ import { createElement } from 'react'; import { msg } from '@lingui/core/macro'; -import type { OrganisationGlobalSettings } from '@prisma/client'; import { OrganisationGroupType, type Team } from '@prisma/client'; +import { uniqueBy } from 'remeda'; import { mailer } from '@documenso/email/mailer'; import { TeamDeleteEmailTemplate } from '@documenso/email/templates/team-delete'; @@ -13,8 +13,8 @@ import { prisma } from '@documenso/prisma'; import { getI18nInstance } from '../../client-only/providers/i18n-server'; import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '../../constants/teams'; +import { jobs } from '../../jobs/client'; import { renderEmailWithI18N } from '../../utils/render-email-with-i18n'; -import { teamGlobalSettingsToBranding } from '../../utils/team-global-settings-to-branding'; import { buildTeamWhereQuery } from '../../utils/teams'; import { getTeamSettings } from './get-team-settings'; @@ -28,6 +28,11 @@ export const deleteTeam = async ({ userId, teamId }: DeleteTeamOptions) => { const team = await prisma.team.findFirst({ where: buildTeamWhereQuery(teamId, userId, TEAM_MEMBER_ROLE_PERMISSIONS_MAP['DELETE_TEAM']), include: { + organisation: { + select: { + organisationGlobalSettings: true, + }, + }, teamGroups: { include: { organisationGroup: { @@ -65,21 +70,19 @@ export const deleteTeam = async ({ userId, teamId }: DeleteTeamOptions) => { teamId, }); + const membersToNotify = uniqueBy( + team.teamGroups.flatMap((group) => + group.organisationGroup.organisationGroupMembers.map((member) => ({ + id: member.organisationMember.user.id, + name: member.organisationMember.user.name || '', + email: member.organisationMember.user.email, + })), + ), + (member) => member.id, + ); + await prisma.$transaction( async (tx) => { - // Todo: orgs handle any subs? - // if (team.subscription) { - // await stripe.subscriptions - // .cancel(team.subscription.planId, { - // prorate: false, - // invoice_now: true, - // }) - // .catch((err) => { - // console.error(err); - // throw AppError.parseError(err); - // }); - // } - await tx.team.delete({ where: { id: teamId, @@ -96,25 +99,20 @@ export const deleteTeam = async ({ userId, teamId }: DeleteTeamOptions) => { }, }); - // const members = team.teamGroups.flatMap((group) => - // group.organisationGroup.organisationMembers.map((member) => ({ - // id: member.user.id, - // name: member.user.name || '', - // email: member.user.email, - // })), - // ); - - // await jobs.triggerJob({ - // name: 'send.team-deleted.email', - // payload: { - // team: { - // name: team.name, - // url: team.url, - // teamGlobalSettings: team.teamGlobalSettings, // Todo: orgs - // }, - // members, - // }, - // }); + await jobs.triggerJob({ + name: 'send.team-deleted.email', + payload: { + team: { + name: team.name, + url: team.url, + // teamGlobalSettings: { + // ...settings, + // teamId: team.id, + // }, + }, + members: membersToNotify, + }, + }); }, { timeout: 30_000 }, ); @@ -122,14 +120,14 @@ export const deleteTeam = async ({ userId, teamId }: DeleteTeamOptions) => { type SendTeamDeleteEmailOptions = { email: string; - team: Pick; - settings: Omit; + team: Pick; + // settings: Omit; }; export const sendTeamDeleteEmail = async ({ email, team, - settings, + // settings, }: SendTeamDeleteEmailOptions) => { const template = createElement(TeamDeleteEmailTemplate, { assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL(), @@ -137,9 +135,12 @@ export const sendTeamDeleteEmail = async ({ teamUrl: team.url, }); - const branding = teamGlobalSettingsToBranding(settings, team.id); + // This is never actually passed on so commenting it out. + // const branding = teamGlobalSettingsToBranding(settings, team.id); + // const lang = settings.documentLanguage; - const lang = settings.documentLanguage; + const branding = undefined; + const lang = undefined; const [html, text] = await Promise.all([ renderEmailWithI18N(template, { lang, branding }), diff --git a/packages/lib/server-only/team/find-teams-pending.ts b/packages/lib/server-only/team/find-teams-pending.ts deleted file mode 100644 index e902c42ad..000000000 --- a/packages/lib/server-only/team/find-teams-pending.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { Team } from '@prisma/client'; -import { Prisma } from '@prisma/client'; -import type { z } from 'zod'; - -import { prisma } from '@documenso/prisma'; -import { TeamPendingSchema } from '@documenso/prisma/generated/zod/modelSchema/TeamPendingSchema'; - -import { type FindResultResponse, ZFindResultResponse } from '../../types/search-params'; - -export interface FindTeamsPendingOptions { - userId: number; - query?: string; - page?: number; - perPage?: number; - orderBy?: { - column: keyof Team; - direction: 'asc' | 'desc'; - }; -} - -export const ZFindTeamsPendingResponseSchema = ZFindResultResponse.extend({ - data: TeamPendingSchema.array(), -}); - -export type TFindTeamsPendingResponse = z.infer; - -export const findTeamsPending = async ({ - userId, - query, - page = 1, - perPage = 10, - orderBy, -}: FindTeamsPendingOptions): Promise => { - const orderByColumn = orderBy?.column ?? 'name'; - const orderByDirection = orderBy?.direction ?? 'desc'; - - const whereClause: Prisma.TeamPendingWhereInput = { - ownerUserId: userId, - }; - - if (query && query.length > 0) { - whereClause.name = { - contains: query, - mode: Prisma.QueryMode.insensitive, - }; - } - - const [data, count] = await Promise.all([ - prisma.teamPending.findMany({ - where: whereClause, - skip: Math.max(page - 1, 0) * perPage, - take: perPage, - orderBy: { - [orderByColumn]: orderByDirection, - }, - }), - prisma.teamPending.count({ - where: whereClause, - }), - ]); - - return { - data, - count, - currentPage: Math.max(page, 1), - perPage, - totalPages: Math.ceil(count / perPage), - } satisfies FindResultResponse; -}; diff --git a/packages/lib/server-only/team/update-team.ts b/packages/lib/server-only/team/update-team.ts index 9995b83a6..32576d6cf 100644 --- a/packages/lib/server-only/team/update-team.ts +++ b/packages/lib/server-only/team/update-team.ts @@ -19,7 +19,7 @@ export type UpdateTeamOptions = { export const updateTeam = async ({ userId, teamId, data }: UpdateTeamOptions): Promise => { try { await prisma.$transaction(async (tx) => { - const foundPendingTeamWithUrl = await tx.teamPending.findFirst({ + const foundTeamWithUrl = await tx.team.findFirst({ where: { url: data.url, }, @@ -31,21 +31,19 @@ export const updateTeam = async ({ userId, teamId, data }: UpdateTeamOptions): P }, }); - if (foundPendingTeamWithUrl || foundOrganisationWithUrl) { + if (foundTeamWithUrl || foundOrganisationWithUrl) { throw new AppError(AppErrorCode.ALREADY_EXISTS, { message: 'Team URL already exists.', }); } - const team = await tx.team.update({ + return await tx.team.update({ where: buildTeamWhereQuery(teamId, userId, TEAM_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_TEAM']), data: { url: data.url, name: data.name, }, }); - - return team; }); } catch (err) { console.error(err); diff --git a/packages/lib/server-only/template/update-template.ts b/packages/lib/server-only/template/update-template.ts index 507c6b723..2227cde14 100644 --- a/packages/lib/server-only/template/update-template.ts +++ b/packages/lib/server-only/template/update-template.ts @@ -1,6 +1,5 @@ import type { DocumentVisibility, Template, TemplateMeta } from '@prisma/client'; -import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; import { prisma } from '@documenso/prisma'; import { AppError, AppErrorCode } from '../../errors/app-error'; @@ -39,6 +38,15 @@ export const updateTemplate = async ({ }, include: { templateMeta: true, + team: { + select: { + organisation: { + select: { + organisationClaim: true, + }, + }, + }, + }, }, }); @@ -66,17 +74,10 @@ export const updateTemplate = async ({ data?.globalActionAuth === undefined ? documentGlobalActionAuth : data.globalActionAuth; // Check if user has permission to set the global action auth. - if (newGlobalActionAuth) { - const isDocumentEnterprise = await isUserEnterprise({ - userId, - teamId, + if (newGlobalActionAuth && !template.team.organisation.organisationClaim.flags.cfr21) { + throw new AppError(AppErrorCode.UNAUTHORIZED, { + message: 'You do not have permission to set the action auth', }); - - if (!isDocumentEnterprise) { - throw new AppError(AppErrorCode.UNAUTHORIZED, { - message: 'You do not have permission to set the action auth', - }); - } } const authOptions = createDocumentAuthOptions({ diff --git a/packages/lib/server-only/user/create-billing-portal.ts b/packages/lib/server-only/user/create-billing-portal.ts deleted file mode 100644 index 4e00ecc79..000000000 --- a/packages/lib/server-only/user/create-billing-portal.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { User } from '@prisma/client'; - -import { getStripeCustomerByUser } from '@documenso/ee/server-only/stripe/get-customer'; -import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-session'; -import { IS_BILLING_ENABLED, NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; - -export type CreateBillingPortalOptions = { - user: Pick; -}; - -export const createBillingPortal = async ({ user }: CreateBillingPortalOptions) => { - if (!IS_BILLING_ENABLED()) { - throw new Error('Billing is not enabled'); - } - - const { stripeCustomer } = await getStripeCustomerByUser(user); - - return getPortalSession({ - customerId: stripeCustomer.id, - returnUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/settings/billing`, - }); -}; diff --git a/packages/lib/server-only/user/create-checkout-session.ts b/packages/lib/server-only/user/create-checkout-session.ts deleted file mode 100644 index 3b6a45d47..000000000 --- a/packages/lib/server-only/user/create-checkout-session.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { User } from '@prisma/client'; - -import { getCheckoutSession } from '@documenso/ee/server-only/stripe/get-checkout-session'; -import { getStripeCustomerByUser } from '@documenso/ee/server-only/stripe/get-customer'; -import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-session'; -import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; - -import { getSubscriptionsByUserId } from '../subscription/get-subscriptions-by-user-id'; - -export type CreateCheckoutSession = { - user: Pick; - priceId: string; -}; - -export const createCheckoutSession = async ({ user, priceId }: CreateCheckoutSession) => { - const { stripeCustomer } = await getStripeCustomerByUser(user); - - const existingSubscriptions = await getSubscriptionsByUserId({ userId: user.id }); - - const foundSubscription = existingSubscriptions.find( - (subscription) => - subscription.priceId === priceId && - subscription.periodEnd && - subscription.periodEnd >= new Date(), - ); - - if (foundSubscription) { - return getPortalSession({ - customerId: stripeCustomer.id, - returnUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/settings/billing`, - }); - } - - return getCheckoutSession({ - customerId: stripeCustomer.id, - priceId, - returnUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/settings/billing`, - }); -}; diff --git a/packages/lib/server-only/user/create-user.ts b/packages/lib/server-only/user/create-user.ts index 0787cd726..53f813c82 100644 --- a/packages/lib/server-only/user/create-user.ts +++ b/packages/lib/server-only/user/create-user.ts @@ -1,10 +1,8 @@ import { hash } from '@node-rs/bcrypt'; import type { User } from '@prisma/client'; -import { OrganisationGroupType, OrganisationMemberInviteStatus } from '@prisma/client'; import { prisma } from '@documenso/prisma'; -import { IS_BILLING_ENABLED } from '../../constants/app'; import { SALT_ROUNDS } from '../../constants/auth'; import { AppError, AppErrorCode } from '../../errors/app-error'; import { createPersonalOrganisation } from '../organisation/create-organisation'; @@ -14,16 +12,9 @@ export interface CreateUserOptions { email: string; password: string; signature?: string | null; - orgUrl: string; } -export const createUser = async ({ - name, - email, - password, - signature, - orgUrl, -}: CreateUserOptions) => { +export const createUser = async ({ name, email, password, signature }: CreateUserOptions) => { const hashedPassword = await hash(password, SALT_ROUNDS); const userExists = await prisma.user.findFirst({ @@ -36,22 +27,6 @@ export const createUser = async ({ throw new AppError(AppErrorCode.ALREADY_EXISTS); } - // Todo: orgs handle htis - if (orgUrl) { - const urlExists = await prisma.team.findFirst({ - where: { - url: orgUrl, - }, - }); - - if (urlExists) { - throw new AppError('PROFILE_URL_TAKEN', { - message: 'Profile username is taken', - userMessage: 'The profile username is already taken', - }); - } - } - const user = await prisma.$transaction(async (tx) => { const user = await tx.user.create({ data: { @@ -76,8 +51,7 @@ export const createUser = async ({ return user; }); - await createPersonalOrganisation({ userId: user.id, orgUrl }); - + // Not used at the moment, uncomment if required. await onCreateUserHook(user).catch((err) => { // Todo: (RR7) Add logging. console.error(err); @@ -87,119 +61,12 @@ export const createUser = async ({ }; /** - * Should be run after a user is created. + * Should be run after a user is created, example during email password signup or google sign in. * * @returns User */ export const onCreateUserHook = async (user: User) => { - const { email } = user; - - const acceptedOrganisationInvites = await prisma.organisationMemberInvite.findMany({ - where: { - status: OrganisationMemberInviteStatus.ACCEPTED, - email: { - equals: email, - mode: 'insensitive', - }, - }, - include: { - organisation: { - include: { - groups: { - where: { - type: OrganisationGroupType.INTERNAL_ORGANISATION, - }, - }, - }, - }, - }, - }); - - // For each team invite, add the user to the organisation and team, then delete the team invite. - // If an error occurs, reset the invitation to not accepted. - await Promise.allSettled( - acceptedOrganisationInvites.map(async (invite) => - prisma - .$transaction( - async (tx) => { - const organisationGroupToUse = invite.organisation.groups.find( - (group) => - group.type === OrganisationGroupType.INTERNAL_ORGANISATION && - group.organisationRole === invite.organisationRole, - ); - - if (!organisationGroupToUse) { - throw new AppError(AppErrorCode.UNKNOWN_ERROR, { - message: 'Organisation group not found', - }); - } - - await tx.organisationMember.create({ - data: { - organisationId: invite.organisationId, - userId: user.id, - organisationGroupMembers: { - create: { - groupId: organisationGroupToUse.id, - }, - }, - }, - }); - - await tx.organisationMemberInvite.delete({ - where: { - id: invite.id, - }, - }); - - if (!IS_BILLING_ENABLED()) { - return; - } - - const organisation = await tx.organisation.findFirstOrThrow({ - where: { - id: invite.organisationId, - }, - include: { - members: { - select: { - id: true, - }, - }, - subscriptions: { - select: { - id: true, - priceId: true, - planId: true, - }, - }, - }, - }); - - // const organisationSeatSubscription = // TODO - - // if (organisation.subscriptions) { - // await updateSubscriptionItemQuantity({ - // priceId: team.subscription.priceId, - // subscriptionId: team.subscription.planId, - // quantity: team.members.length, - // }); - // } - }, - { timeout: 30_000 }, - ) - .catch(async () => { - await prisma.organisationMemberInvite.update({ - where: { - id: invite.id, - }, - data: { - status: OrganisationMemberInviteStatus.PENDING, - }, - }); - }), - ), - ); + await createPersonalOrganisation({ userId: user.id }); return user; }; diff --git a/packages/lib/translations/de/web.po b/packages/lib/translations/de/web.po index ae8003ed8..4a0431dc8 100644 --- a/packages/lib/translations/de/web.po +++ b/packages/lib/translations/de/web.po @@ -73,11 +73,6 @@ msgstr "{0, plural, one {# Zeichen über dem Limit} other {# Zeichen über dem L msgid "{0, plural, one {# recipient} other {# recipients}}" msgstr "{0, plural, one {# Empfänger} other {# Empfänger}}" -#. placeholder {0}: row.original.quantity -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx -msgid "{0, plural, one {# Seat} other {# Seats}}" -msgstr "{0, plural, one {# Sitz} other {# Sitze}}" - #. placeholder {0}: org.teams.length #: apps/remix/app/routes/_authenticated+/dashboard.tsx msgid "{0, plural, one {# team} other {# teams}}" @@ -132,16 +127,6 @@ msgstr "{0} hat Sie eingeladen, das Dokument \"{1}\" {recipientActionVerb}." msgid "{0} invited you to {recipientActionVerb} a document" msgstr "{0} hat dich eingeladen, ein Dokument {recipientActionVerb}" -#. placeholder {0}: memberName || memberEmail -#: packages/email/templates/team-join.tsx -msgid "{0} joined the team {teamName} on Documenso" -msgstr "{0} ist dem Team {teamName} bei Documenso beigetreten" - -#. placeholder {0}: memberName || memberEmail -#: packages/email/templates/team-leave.tsx -msgid "{0} left the team {teamName} on Documenso" -msgstr "{0} hat das Team {teamName} bei Documenso verlassen" - #. placeholder {0}: remaining.documents #. placeholder {1}: quota.documents #: apps/remix/app/components/general/document/document-upload.tsx @@ -223,14 +208,6 @@ msgstr "{inviterName} im Namen von \"{teamName}\" hat dich eingeladen, {0}<0/>\" msgid "{inviterName} on behalf of \"{teamName}\" has invited you to {action} {documentName}" msgstr "{inviterName} im Namen von \"{teamName}\" hat Sie eingeladen, das Dokument {documentName} {action}" -#: packages/email/templates/team-join.tsx -msgid "{memberEmail} joined the following team" -msgstr "{memberEmail} ist dem folgenden Team beigetreten" - -#: packages/email/templates/team-leave.tsx -msgid "{memberEmail} left the following team" -msgstr "{memberEmail} hat das folgende Team verlassen" - #: packages/lib/utils/document-audit-logs.ts msgid "{prefix} added a field" msgstr "{prefix} hat ein Feld hinzugefügt" @@ -484,6 +461,14 @@ msgstr "<0>Sie sind dabei, die Unterzeichnung von \"<1>{documentTitle}\" abz msgid "<0>You are about to complete viewing \"<1>{documentTitle}\".<2/> Are you sure?" msgstr "<0>Sie sind dabei, die Ansicht von \"<1>{documentTitle}\" abzuschließen.<2/> Sind Sie sicher?" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "0 Free organisations left" +msgstr "" + +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "1 Free organisations left" +msgstr "" + #: apps/remix/app/components/forms/token.tsx msgid "1 month" msgstr "1 Monat" @@ -510,6 +495,7 @@ msgid "404 Organisation group not found" msgstr "" #: apps/remix/app/routes/_authenticated+/_layout.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "404 Organisation not found" msgstr "" @@ -522,6 +508,14 @@ msgstr "404 Profil nicht gefunden" msgid "404 Team not found" msgstr "404 Team nicht gefunden" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "5 documents a month" +msgstr "" + +#: packages/lib/types/subscription.ts +msgid "5 Documents a month" +msgstr "" + #: apps/remix/app/components/general/generic-error-layout.tsx msgid "500 Internal Server Error" msgstr "" @@ -566,9 +560,29 @@ msgstr "Ein Feld wurde aktualisiert" msgid "A means to print or download documents for your records" msgstr "Ein Mittel, um Dokumente für Ihre Unterlagen zu drucken oder herunterzuladen" -#: packages/lib/jobs/definitions/emails/send-team-member-joined-email.handler.ts -msgid "A new member has joined your team" -msgstr "Ein neues Mitglied ist deinem Team beigetreten" +#: packages/email/templates/organisation-join.tsx +msgid "A member has joined your organisation on Documenso" +msgstr "" + +#: packages/lib/jobs/definitions/emails/send-organisation-member-left-email.handler.ts +msgid "A member has left your organisation" +msgstr "" + +#: packages/email/templates/organisation-leave.tsx +msgid "A member has left your organisation {organisationName}" +msgstr "" + +#: packages/email/templates/organisation-leave.tsx +msgid "A member has left your organisation on Documenso" +msgstr "" + +#: packages/lib/jobs/definitions/emails/send-organisation-member-joined-email.handler.ts +msgid "A new member has joined your organisation" +msgstr "" + +#: packages/email/templates/organisation-join.tsx +msgid "A new member has joined your organisation {organisationName}" +msgstr "" #: apps/remix/app/components/forms/token.tsx msgid "A new token was created successfully." @@ -608,19 +622,6 @@ msgstr "Ein Geheimnis, das an deine URL gesendet wird, damit du überprüfen kan msgid "A stable internet connection" msgstr "Eine stabile Internetverbindung" -#: packages/email/templates/team-join.tsx -msgid "A team member has joined a team on Documenso" -msgstr "Ein Teammitglied ist einem Team bei Documenso beigetreten" - -#. placeholder {0}: team.name -#: packages/lib/jobs/definitions/emails/send-team-member-left-email.handler.ts -msgid "A team member has left {0}" -msgstr "Ein Teammitglied hat {0} verlassen" - -#: packages/email/templates/team-leave.tsx -msgid "A team member has left a team on Documenso" -msgstr "Ein Teammitglied hat ein Team auf Documenso verlassen" - #: packages/email/templates/team-delete.tsx #: packages/email/templates/team-delete.tsx msgid "A team you were a part of has been deleted" @@ -630,8 +631,11 @@ msgstr "Ein Team, dem du angehörtest, wurde gelöscht" msgid "A unique URL to access your profile" msgstr "Eine eindeutige URL, um auf dein Profil zuzugreifen" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "A unique URL to identify the organisation" +msgstr "" + #: apps/remix/app/components/forms/organisation-update-form.tsx -#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "A unique URL to identify your organisation" msgstr "" @@ -651,8 +655,8 @@ msgid "Accept" msgstr "Akzeptieren" #: packages/email/templates/organisation-invite.tsx -msgid "Accept invitation to join a team on Documenso" -msgstr "Einladung annehmen, um einem Team auf Documenso beizutreten" +msgid "Accept invitation to join an organisation on Documenso" +msgstr "" #: packages/email/templates/confirm-team-email.tsx msgid "Accept team email request for {teamName} on Documenso" @@ -720,11 +724,12 @@ msgstr "Aktion" #: apps/remix/app/components/tables/organisation-groups-table.tsx #: apps/remix/app/components/tables/inbox-table.tsx #: apps/remix/app/components/tables/documents-table.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx #: apps/remix/app/components/general/template/template-page-view-documents-table.tsx msgid "Actions" msgstr "Aktionen" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.teams.tsx #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.members.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx msgid "Active" @@ -865,11 +870,7 @@ msgstr "Fügen Sie die Personen hinzu, die das Dokument unterschreiben werden." msgid "Add the recipients to create the document with" msgstr "Fügen Sie die Empfänger hinzu, um das Dokument zu erstellen" -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "Adding and removing seats will adjust your invoice accordingly." -msgstr "Das Hinzufügen und Entfernen von Sitzplätzen wird Ihre Rechnung entsprechend anpassen." - -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Additional brand information to display at the bottom of emails" msgstr "Zusätzliche Markeninformationen, die am Ende von E-Mails angezeigt werden sollen" @@ -886,6 +887,10 @@ msgstr "Admin-Aktionen" msgid "Admin panel" msgstr "Admin-Panel" +#: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx +msgid "Admin Panel" +msgstr "" + #: packages/ui/primitives/template-flow/add-template-settings.tsx #: packages/ui/primitives/document-flow/add-settings.tsx msgid "Advanced Options" @@ -962,6 +967,10 @@ msgstr "" msgid "Allowed Signature Types" msgstr "" +#: apps/remix/app/components/tables/admin-claims-table.tsx +msgid "Allowed teams" +msgstr "" + #: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx msgid "Allows authenticating using biometrics, password managers, hardware keys, etc." msgstr "Erlaubt die Authentifizierung mit biometrischen Daten, Passwort-Managern, Hardware-Schlüsseln usw." @@ -970,7 +979,7 @@ msgstr "Erlaubt die Authentifizierung mit biometrischen Daten, Passwort-Managern msgid "Already have an account? <0>Sign in instead" msgstr "Hast du bereits ein Konto? <0>Stattdessen anmelden" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx msgid "Amount" msgstr "Betrag" @@ -991,6 +1000,8 @@ msgid "An email containing an invitation will be sent to each member." msgstr "Eine E-Mail mit einer Einladung wird an jedes Mitglied gesendet." #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/general/claim-account.tsx #: apps/remix/app/components/forms/token.tsx #: apps/remix/app/components/forms/signup.tsx @@ -1230,6 +1241,10 @@ msgstr "Genehmigung" msgid "Are you sure you want to complete the document? This action cannot be undone. Please ensure that you have completed prefilling all relevant fields before proceeding." msgstr "" +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "Are you sure you want to delete the following claim?" +msgstr "" + #: apps/remix/app/components/dialogs/token-delete-dialog.tsx msgid "Are you sure you want to delete this token?" msgstr "Bist du sicher, dass du dieses Token löschen möchtest?" @@ -1340,9 +1355,9 @@ msgid "Awaiting email confirmation" msgstr "Warte auf E-Mail-Bestätigung" #: apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx -#: apps/remix/app/components/forms/signup.tsx #: apps/remix/app/components/dialogs/team-member-create-dialog.tsx #: apps/remix/app/components/dialogs/team-group-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Back" msgstr "Zurück" @@ -1367,29 +1382,17 @@ msgstr "Backup-Codes" msgid "Banner Updated" msgstr "Banner aktualisiert" -#: apps/remix/app/components/forms/signup.tsx -msgid "Basic details" -msgstr "Basisdetails" - #: packages/email/template-components/template-confirmation-email.tsx msgid "Before you get started, please confirm your email address by clicking the button below:" msgstr "Bitte bestätige vor dem Start deine E-Mail-Adresse, indem du auf den Button unten klickst:" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings._layout.tsx -#: apps/remix/app/routes/_authenticated+/settings+/billing.tsx -#: apps/remix/app/components/general/settings-nav-mobile.tsx -#: apps/remix/app/components/general/settings-nav-desktop.tsx #: apps/remix/app/components/general/teams/team-settings-nav-mobile.tsx #: apps/remix/app/components/general/teams/team-settings-nav-desktop.tsx msgid "Billing" msgstr "Abrechnung" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx -#: apps/remix/app/routes/_authenticated+/settings+/billing.tsx -msgid "Billing has been moved to organisations" -msgstr "" - #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx msgid "Black" msgstr "Schwarz" @@ -1398,12 +1401,13 @@ msgstr "Schwarz" msgid "Blue" msgstr "Blau" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx msgid "Branding Preferences" msgstr "Markenpräferenzen" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx msgid "Branding preferences updated" msgstr "Markenpräferenzen aktualisiert" @@ -1512,7 +1516,7 @@ msgstr "" #: apps/remix/app/components/dialogs/team-email-add-dialog.tsx #: apps/remix/app/components/dialogs/team-delete-dialog.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx +#: apps/remix/app/components/dialogs/team-create-dialog.tsx #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx #: apps/remix/app/components/dialogs/organisation-member-update-dialog.tsx @@ -1523,9 +1527,13 @@ msgstr "" #: apps/remix/app/components/dialogs/organisation-group-create-dialog.tsx #: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx #: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx #: apps/remix/app/components/dialogs/document-resend-dialog.tsx #: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx #: apps/remix/app/components/dialogs/document-delete-dialog.tsx +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx #: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx #: packages/ui/primitives/signature-pad/signature-pad-dialog.tsx #: packages/ui/primitives/document-flow/send-document-action-dialog.tsx @@ -1574,7 +1582,7 @@ msgstr "Kontrollkästchen" msgid "Checkbox values" msgstr "Checkbox-Werte" -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Checkout" msgstr "Abrechnung" @@ -1598,13 +1606,9 @@ msgstr "Wählen..." msgid "Claim account" msgstr "Konto beanspruchen" -#: apps/remix/app/components/forms/signup.tsx -msgid "Claim username" -msgstr "Benutzername beanspruchen" - -#: apps/remix/app/components/forms/signup.tsx -msgid "Claim your username now" -msgstr "Benutzername jetzt beanspruchen" +#: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx +msgid "Claims" +msgstr "" #: apps/remix/app/components/dialogs/template-use-dialog.tsx msgid "Clear file" @@ -1660,6 +1664,10 @@ msgstr "Klicken, um das Feld auszufüllen" msgid "Close" msgstr "Schließen" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Compare all plans and features in detail" +msgstr "" + #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx #: apps/remix/app/components/forms/signup.tsx @@ -1782,6 +1790,10 @@ msgstr "Zustimmung zu elektronischen Transaktionen" msgid "Contact Information" msgstr "Kontaktinformationen" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Contact sales here" +msgstr "" + #: apps/remix/app/routes/_authenticated+/admin+/site-settings.tsx msgid "Content" msgstr "Inhalt" @@ -1793,6 +1805,7 @@ msgstr "Inhalt" #: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx #: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx #: packages/ui/primitives/document-flow/document-flow-root.tsx msgid "Continue" @@ -1892,6 +1905,7 @@ msgstr "Token kopieren" #: apps/remix/app/routes/_profile+/_layout.tsx #: apps/remix/app/components/dialogs/webhook-create-dialog.tsx #: apps/remix/app/components/dialogs/organisation-group-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Create" msgstr "Erstellen" @@ -1928,6 +1942,14 @@ msgstr "Als Entwurf erstellen" msgid "Create as pending" msgstr "Als ausstehend erstellen" +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Create claim" +msgstr "" + +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Create Claim" +msgstr "" + #: apps/remix/app/components/dialogs/template-direct-link-dialog-wrapper.tsx msgid "Create Direct Link" msgstr "Direkten Link erstellen" @@ -1962,18 +1984,26 @@ msgstr "Einen automatisch erstellen" msgid "Create organisation" msgstr "" -#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx -msgid "Create Organisation" -msgstr "" - #: apps/remix/app/components/general/menu-switcher.tsx -msgid "Create Organization" +msgid "Create Organisation" msgstr "" #: apps/remix/app/components/dialogs/template-use-dialog.tsx msgid "Create signing links" msgstr "Unterzeichnung Links erstellen" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Create Stripe customer" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Create subscription" +msgstr "" + +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Create Subscription Claim" +msgstr "" + #: apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx @@ -2025,7 +2055,7 @@ msgid "Created" msgstr "Erstellt" #: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Created At" msgstr "Erstellt am" @@ -2034,7 +2064,6 @@ msgid "Created by" msgstr "Erstellt von" #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx -#: apps/remix/app/components/tables/organisation-pending-teams-table.tsx msgid "Created on" msgstr "Erstellt am" @@ -2065,12 +2094,16 @@ msgstr "Aktuelle Empfänger:" msgid "Currently all organisation members can access this team" msgstr "" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups._index.tsx -msgid "Custom Organisation Groups" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx +msgid "Currently branding can only be configured for Teams and above plans." msgstr "" -#: apps/remix/app/components/general/billing-plans.tsx -msgid "Daily" +#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx +msgid "Currently branding can only be configured on the organisation level." +msgstr "" + +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups._index.tsx +msgid "Custom Organisation Groups" msgstr "" #: apps/remix/app/components/general/app-command-menu.tsx @@ -2130,16 +2163,20 @@ msgstr "löschen" #: apps/remix/app/components/tables/organisation-teams-table.tsx #: apps/remix/app/components/tables/organisation-groups-table.tsx #: apps/remix/app/components/tables/documents-table-action-dropdown.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx #: apps/remix/app/components/general/document/document-page-view-dropdown.tsx #: apps/remix/app/components/dialogs/webhook-delete-dialog.tsx #: apps/remix/app/components/dialogs/token-delete-dialog.tsx #: apps/remix/app/components/dialogs/template-delete-dialog.tsx #: apps/remix/app/components/dialogs/team-group-delete-dialog.tsx #: apps/remix/app/components/dialogs/team-delete-dialog.tsx +#: apps/remix/app/components/dialogs/team-delete-dialog.tsx #: apps/remix/app/components/dialogs/organisation-member-delete-dialog.tsx #: apps/remix/app/components/dialogs/organisation-group-delete-dialog.tsx #: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx #: apps/remix/app/components/dialogs/document-delete-dialog.tsx +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx msgid "Delete" msgstr "Löschen" @@ -2179,7 +2216,6 @@ msgid "Delete Document" msgstr "Dokument löschen" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.general.tsx -#: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx msgid "Delete organisation" msgstr "" @@ -2195,8 +2231,11 @@ msgstr "" msgid "Delete passkey" msgstr "Passkey löschen" +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "Delete Subscription Claim" +msgstr "" + #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx -#: apps/remix/app/components/dialogs/team-delete-dialog.tsx msgid "Delete team" msgstr "Team löschen" @@ -2620,7 +2659,7 @@ msgstr "Dokumente angesehen" msgid "Don't have an account? <0>Sign up" msgstr "Haben Sie kein Konto? <0>Registrieren" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx #: apps/remix/app/components/tables/documents-table-action-dropdown.tsx #: apps/remix/app/components/tables/documents-table-action-button.tsx #: apps/remix/app/components/general/document/document-page-view-dropdown.tsx @@ -2675,7 +2714,7 @@ msgstr "Dropdown" msgid "Dropdown options" msgstr "Dropdown-Optionen" -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Due to an unpaid invoice, your team has been restricted. Please settle the payment to restore full access to your team." msgstr "Aufgrund einer unbezahlten Rechnung wurde Ihrem Team der Zugriff eingeschränkt. Bitte begleichen Sie die Zahlung, um den vollumfänglichen Zugang zu Ihrem Team wiederherzustellen." @@ -2718,6 +2757,7 @@ msgstr "Offenlegung der elektronischen Unterschrift" #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-dashboard-users-table.tsx #: apps/remix/app/components/general/document-signing/document-signing-email-field.tsx @@ -2790,6 +2830,10 @@ msgstr "E-Mail-Verifizierung wurde entfernt" msgid "Email verification has been resent" msgstr "E-Mail-Verifizierung wurde erneut gesendet" +#: packages/lib/types/subscription.ts +msgid "Embedding, 5 members included and more" +msgstr "" + #: packages/ui/primitives/document-flow/add-fields.tsx msgid "Empty field" msgstr "Leeres Feld" @@ -2820,7 +2864,7 @@ msgstr "Konto Aktivieren" msgid "Enable Authenticator App" msgstr "Authenticator-App aktivieren" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Enable custom branding for all documents in this team." msgstr "Aktivieren Sie individuelles Branding für alle Dokumente in diesem Team." @@ -2849,11 +2893,11 @@ msgstr "Das Aktivieren des Kontos führt dazu, dass der Benutzer das Konto wiede msgid "Enclosed Document" msgstr "Beigefügte Dokument" -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx -msgid "Ends On" -msgstr "Endet am" +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Enter claim name" +msgstr "" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Enter your brand details" msgstr "Geben Sie Ihre Markendaten ein" @@ -2873,7 +2917,12 @@ msgstr "Geben Sie Ihren Namen ein" msgid "Enter your text here" msgstr "Geben Sie hier Ihren Text ein" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Enterprise" +msgstr "" + #: apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx #: apps/remix/app/components/general/verify-email-banner.tsx #: apps/remix/app/components/general/template/template-edit-form.tsx @@ -2948,6 +2997,14 @@ msgstr "Läuft ab am {0}" msgid "External ID" msgstr "Externe ID" +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Failed to create subscription claim." +msgstr "" + +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "Failed to delete subscription claim." +msgstr "" + #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx msgid "Failed to reseal document" msgstr "Dokument konnte nicht erneut versiegelt werden" @@ -2960,6 +3017,10 @@ msgstr "Einstellungen konnten nicht gespeichert werden." msgid "Failed to update recipient" msgstr "Empfänger konnte nicht aktualisiert werden" +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Failed to update subscription claim." +msgstr "" + #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx msgid "Failed to update webhook" msgstr "Webhook konnte nicht aktualisiert werden" @@ -2968,6 +3029,12 @@ msgstr "Webhook konnte nicht aktualisiert werden" msgid "Failed: {failedCount}" msgstr "Fehlgeschlagen: {failedCount}" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Feature Flags" +msgstr "" + #: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx msgid "Field character limit" msgstr "Zeichenbeschränkung des Feldes" @@ -3019,6 +3086,10 @@ msgstr "Die Datei darf nicht größer als {APP_DOCUMENT_UPLOAD_SIZE_LIMIT} MB se msgid "File size exceeds the limit of {APP_DOCUMENT_UPLOAD_SIZE_LIMIT} MB" msgstr "Dateigröße überschreitet das Limit von {APP_DOCUMENT_UPLOAD_SIZE_LIMIT} MB" +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Fill in the details to create a new subscription claim." +msgstr "" + #: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx #: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx #: packages/ui/primitives/document-flow/field-items-advanced-settings/name-field.tsx @@ -3036,6 +3107,10 @@ msgstr "Für Fragen zu dieser Offenlegung, elektronischen Unterschriften oder ei msgid "For each recipient, provide their email (required) and name (optional) in separate columns. Download the template CSV below for the correct format." msgstr "Für jeden Empfänger geben Sie dessen E-Mail (erforderlich) und Namen (optional) in separaten Spalten an. Laden Sie unten die CSV-Vorlage für das korrekte Format herunter." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "For example, if the claim has a new flag \"FLAG_1\" set to true, then this organisation will get that flag added." +msgstr "" + #: packages/lib/server-only/auth/send-forgot-password.ts msgid "Forgot Password?" msgstr "Passwort vergessen?" @@ -3046,6 +3121,10 @@ msgstr "Passwort vergessen?" msgid "Forgot your password?" msgstr "Hast du dein Passwort vergessen?" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Free" +msgstr "" + #: packages/ui/primitives/document-flow/types.ts msgid "Free Signature" msgstr "Freie Unterschrift" @@ -3079,6 +3158,7 @@ msgid "Global recipient action authentication" msgstr "Globale Empfängerauthentifizierung" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "Go back" msgstr "" @@ -3193,7 +3273,6 @@ msgid "Here you can manage your password and security settings." msgstr "Hier können Sie Ihre Passwort- und Sicherheitseinstellungen verwalten." #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx -#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx msgid "Here you can set preferences and defaults for branding." msgstr "Hier können Sie Präferenzen und Voreinstellungen für das Branding festlegen." @@ -3260,6 +3339,14 @@ msgstr "Ich bin der Besitzer dieses Dokuments" msgid "I'm sure! Delete it" msgstr "Ich bin mir sicher! Löschen" +#: apps/remix/app/components/tables/admin-claims-table.tsx +msgid "ID" +msgstr "" + +#: apps/remix/app/components/tables/admin-claims-table.tsx +msgid "ID copied to clipboard" +msgstr "" + #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx msgid "If you do not want to use the authenticator prompted, you can close it, which will then display the next available authenticator." msgstr "Wenn Sie die angegebene Authentifizierung nicht verwenden möchten, können Sie sie schließen, wodurch die nächste verfügbare Authentifizierung angezeigt wird." @@ -3298,6 +3385,7 @@ msgstr "Authentifizierungsmethode erben" #: apps/remix/app/components/forms/document-preferences-form.tsx #: apps/remix/app/components/forms/document-preferences-form.tsx #: apps/remix/app/components/forms/document-preferences-form.tsx +#: apps/remix/app/components/forms/document-preferences-form.tsx msgid "Inherit from organisation" msgstr "" @@ -3305,6 +3393,11 @@ msgstr "" msgid "Inherit organisation members" msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Inherited subscription claim" +msgstr "" + #: apps/remix/app/components/general/document-signing/document-signing-initials-field.tsx #: packages/ui/primitives/document-flow/types.ts msgid "Initials" @@ -3385,7 +3478,7 @@ msgstr "" msgid "Invited At" msgstr "Eingeladen am" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx msgid "Invoice" msgstr "Rechnung" @@ -3479,7 +3572,7 @@ msgstr "Zuletzt verwendet" msgid "Leaderboard" msgstr "Bestenliste" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/dialogs/organisation-leave-dialog.tsx msgid "Leave" msgstr "Verlassen" @@ -3541,8 +3634,10 @@ msgstr "Wird geladen..." msgid "Login" msgstr "Anmelden" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/tables/organisation-teams-table.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Manage" msgstr "Verwalten" @@ -3555,10 +3650,18 @@ msgstr "Verwalten Sie das Profil von {0}" msgid "Manage all organisations you are currently associated with." msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/claims.tsx +msgid "Manage all subscription claims" +msgstr "" + #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/templates.$id._index.tsx msgid "Manage and view template" msgstr "Vorlage verwalten und anzeigen" +#: apps/remix/app/components/general/organisations/organisation-billing-portal-button.tsx +msgid "Manage billing" +msgstr "" + #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx msgid "Manage details for this public template" msgstr "Details für diese öffentliche Vorlage verwalten" @@ -3571,6 +3674,14 @@ msgstr "Direktlink verwalten" msgid "Manage documents" msgstr "Dokumente verwalten" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Manage organisation" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx +msgid "Manage organisations" +msgstr "" + #: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx msgid "Manage passkeys" msgstr "Passkeys verwalten" @@ -3579,17 +3690,20 @@ msgstr "Passkeys verwalten" msgid "Manage permissions and access controls" msgstr "" -#: apps/remix/app/components/general/teams/team-billing-portal-button.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "Manage subscription" msgstr "Abonnement verwalten" -#: apps/remix/app/components/general/billing-portal-button.tsx -msgid "Manage Subscription" +#. placeholder {0}: organisation.name +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Manage the {0} organisation" msgstr "" -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx -msgid "Manage subscriptions" -msgstr "Abonnements verwalten" +#. placeholder {1}: organisation.name +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Manage the {1} organisation subscription" +msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups._index.tsx msgid "Manage the custom groups of members for your organisation." @@ -3661,13 +3775,19 @@ msgstr "Max" msgid "Maximum file size: 4MB. Maximum 100 rows per upload. Blank values will use template defaults." msgstr "Maximale Dateigröße: 4MB. Maximal 100 Zeilen pro Upload. Leere Werte verwenden die Vorlagenstandards." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/dialogs/team-member-create-dialog.tsx #: packages/lib/constants/teams.ts #: packages/lib/constants/organisations.ts msgid "Member" msgstr "Mitglied" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Member Count" +msgstr "" + +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/tables/organisation-members-table.tsx msgid "Member Since" msgstr "Mitglied seit" @@ -3698,7 +3818,12 @@ msgstr "Min" msgid "Modify recipients" msgstr "Empfänger ändern" +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Modify the details of the subscription claim." +msgstr "" + #: apps/remix/app/components/general/billing-plans.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Monthly" msgstr "Monatlich" @@ -3716,9 +3841,11 @@ msgstr "Monatlich aktive Benutzer: Benutzer, die mindestens eines ihrer Dokument #: apps/remix/app/components/tables/admin-leaderboard-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-dashboard-users-table.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx #: apps/remix/app/components/general/claim-account.tsx #: apps/remix/app/components/general/document-signing/document-signing-name-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx @@ -3771,7 +3898,6 @@ msgstr "" msgid "New Template" msgstr "Neue Vorlage" -#: apps/remix/app/components/forms/signup.tsx #: apps/remix/app/components/embed/embed-document-signing-page.tsx #: apps/remix/app/components/embed/embed-direct-template-client-page.tsx #: apps/remix/app/components/dialogs/team-member-create-dialog.tsx @@ -3846,6 +3972,10 @@ msgstr "Keine Ergebnisse gefunden." msgid "No signature field found" msgstr "Kein Unterschriftsfeld gefunden" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "No Stripe customer attached" +msgstr "" + #: apps/remix/app/components/tables/team-groups-table.tsx msgid "No team groups found" msgstr "" @@ -3873,6 +4003,7 @@ msgstr "Kein Wert gefunden." msgid "No worries, it happens! Enter your email and we'll email you a special link to reset your password." msgstr "Keine Sorge, das passiert! Geben Sie Ihre E-Mail ein, und wir senden Ihnen einen speziellen Link zum Zurücksetzen Ihres Passworts." +#: apps/remix/app/components/tables/admin-claims-table.tsx #: packages/lib/constants/document.ts msgid "None" msgstr "Keine" @@ -3897,6 +4028,16 @@ msgstr "Nummer" msgid "Number format" msgstr "Zahlenformat" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Number of members allowed. 0 = Unlimited" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Number of teams allowed. 0 = Unlimited" +msgstr "" + #. placeholder {0}: document.team?.name #: apps/remix/app/components/general/document-signing/document-signing-page-view.tsx msgid "on behalf of \"{0}\" has invited you to approve this document" @@ -3957,10 +4098,6 @@ msgstr "Nur Administratoren können auf das Dokument zugreifen und es anzeigen" msgid "Only managers and above can access and view the document" msgstr "Nur Manager und darüber können auf das Dokument zugreifen und es anzeigen" -#: apps/remix/app/components/forms/signup.tsx -msgid "Only subscribers can have a username shorter than 6 characters" -msgstr "Nur Abonnenten können einen Benutzernamen mit weniger als 6 Zeichen haben" - #: apps/remix/app/routes/_profile+/_layout.tsx #: apps/remix/app/components/general/generic-error-layout.tsx #: apps/remix/app/components/general/generic-error-layout.tsx @@ -3980,7 +4117,8 @@ msgstr "Oder" msgid "Or continue with" msgstr "Oder fahren Sie fort mit" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Organisation" msgstr "" @@ -3996,6 +4134,11 @@ msgstr "" msgid "Organisation Group Settings" msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Organisation has been updated successfully" +msgstr "" + #: apps/remix/app/routes/_unauthenticated+/organisation.invite.$token.tsx msgid "Organisation invitation" msgstr "" @@ -4015,15 +4158,18 @@ msgid "Organisation Member" msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.members.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "Organisation Members" msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/forms/organisation-update-form.tsx #: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Organisation Name" msgstr "" #: apps/remix/app/routes/_authenticated+/_layout.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "Organisation not found" msgstr "" @@ -4036,6 +4182,7 @@ msgid "Organisation role" msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/components/dialogs/organisation-member-invite-dialog.tsx msgid "Organisation Role" msgstr "" @@ -4047,13 +4194,18 @@ msgstr "" msgid "Organisation Settings" msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Organisation Teams" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/forms/organisation-update-form.tsx -#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Organisation URL" msgstr "" #: apps/remix/app/routes/_authenticated+/dashboard.tsx #: apps/remix/app/routes/_authenticated+/settings+/organisations.tsx +#: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx #: apps/remix/app/components/general/settings-nav-mobile.tsx #: apps/remix/app/components/general/settings-nav-desktop.tsx #: apps/remix/app/components/general/menu-switcher.tsx @@ -4076,8 +4228,9 @@ msgstr "Andernfalls wird das Dokument als Entwurf erstellt." #: apps/remix/app/routes/_authenticated+/dashboard.tsx #: apps/remix/app/routes/_authenticated+/dashboard.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/tables/organisation-members-table.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Owner" msgstr "Besitzer" @@ -4092,10 +4245,6 @@ msgstr "Seite {0} von {1}" msgid "Page {0} of {numPages}" msgstr "Seite {0} von {numPages}" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx -msgid "Paid" -msgstr "Bezahlt" - #: apps/remix/app/components/forms/signin.tsx msgid "Passkey" msgstr "Passkey" @@ -4166,20 +4315,11 @@ msgstr "Passwort aktualisiert" msgid "Password updated!" msgstr "Passwort aktualisiert!" -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx -msgid "Pay" -msgstr "Bezahle" - -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "Payment is required to finalise the creation of your team." -msgstr "Zahlung ist erforderlich, um die Erstellung Ihres Teams abzuschließen." - -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Payment overdue" msgstr "Zahlung überfällig" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.teams.tsx #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.members.tsx #: apps/remix/app/components/general/template/template-page-view-documents-table.tsx #: apps/remix/app/components/general/document/document-status.tsx @@ -4205,9 +4345,15 @@ msgstr "Ausstehende Dokumente" msgid "Pending invitations" msgstr "Ausstehende Einladungen" -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx -msgid "Pending team deleted." -msgstr "Ausstehendes Team gelöscht." +#: apps/remix/app/components/general/billing-plans.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "per month" +msgstr "" + +#: apps/remix/app/components/general/billing-plans.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "per year" +msgstr "" #: apps/remix/app/components/general/menu-switcher.tsx msgid "Personal Account" @@ -4369,6 +4515,10 @@ msgstr "Bitte {0} eingeben, um zu bestätigen" msgid "Please type <0>{0} to confirm." msgstr "Bitte geben Sie <0>{0} ein, um zu bestätigen." +#: apps/remix/app/components/forms/branding-preferences-form.tsx +msgid "Please upload a logo" +msgstr "" + #: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx msgid "Pre-formatted CSV template with example data." msgstr "Vorformatiertes CSV-Template mit Beispieldaten." @@ -4435,10 +4585,6 @@ msgstr "Öffentliches Profil" msgid "Public profile URL" msgstr "Öffentlicher Profil-URL" -#: apps/remix/app/components/forms/signup.tsx -msgid "Public profile username" -msgstr "Öffentlicher Profil-Benutzername" - #: apps/remix/app/components/tables/templates-table.tsx msgid "Public templates are connected to your public profile. Any modifications to public templates will also appear in your public profile." msgstr "Öffentliche Vorlagen sind mit Ihrem öffentlichen Profil verbunden. Änderungen an öffentlichen Vorlagen erscheinen auch in Ihrem öffentlichen Profil." @@ -4621,7 +4767,6 @@ msgstr "Erinnerung: Bitte {recipientActionVerb} dieses Dokument" msgid "Reminder: Please {recipientActionVerb} your document" msgstr "Erinnerung: Bitte {recipientActionVerb} dein Dokument" -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx #: apps/remix/app/components/tables/team-members-table.tsx #: apps/remix/app/components/tables/team-groups-table.tsx #: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx @@ -4630,7 +4775,7 @@ msgstr "Erinnerung: Bitte {recipientActionVerb} dein Dokument" #: apps/remix/app/components/general/teams/team-email-dropdown.tsx #: apps/remix/app/components/general/document-signing/document-signing-field-container.tsx #: apps/remix/app/components/general/document-signing/document-signing-field-container.tsx -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx #: apps/remix/app/components/forms/avatar-image.tsx #: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx #: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx @@ -4710,11 +4855,11 @@ msgstr "Passwort zurücksetzen" msgid "Resetting Password..." msgstr "Passwort wird zurückgesetzt..." -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Resolve" msgstr "Zahlung klären" -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Resolve payment" msgstr "Zahlung klären" @@ -4754,7 +4899,7 @@ msgstr "Zugriff widerrufen" msgid "Revoke access" msgstr "Zugriff widerrufen" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/tables/team-members-table.tsx #: apps/remix/app/components/tables/team-groups-table.tsx #: apps/remix/app/components/tables/organisation-members-table.tsx @@ -4764,7 +4909,6 @@ msgstr "Zugriff widerrufen" #: apps/remix/app/components/dialogs/team-member-update-dialog.tsx #: apps/remix/app/components/dialogs/team-group-update-dialog.tsx #: apps/remix/app/components/dialogs/organisation-member-update-dialog.tsx -#: apps/remix/app/components/dialogs/organisation-member-invite-dialog.tsx msgid "Role" msgstr "Rolle" @@ -4797,6 +4941,14 @@ msgstr "Vorlage speichern" msgid "Search" msgstr "Suchen" +#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx +msgid "Search and manage all organisations" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/claims.tsx +msgid "Search by claim ID or name" +msgstr "" + #: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx msgid "Search by document title" msgstr "Nach Dokumenttitel suchen" @@ -4806,6 +4958,10 @@ msgstr "Nach Dokumenttitel suchen" msgid "Search by name or email" msgstr "Nach Name oder E-Mail suchen" +#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx +msgid "Search by organisation ID, name, customer ID or owner email" +msgstr "" + #: apps/remix/app/components/general/document/document-search.tsx msgid "Search documents..." msgstr "Dokumente suchen..." @@ -4834,6 +4990,14 @@ msgstr "Sicherheitsaktivität" msgid "Select" msgstr "Auswählen" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Select a plan" +msgstr "" + +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Select a plan to continue" +msgstr "" + #: apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx msgid "Select a team to view its dashboard" msgstr "" @@ -4889,6 +5053,10 @@ msgstr "" msgid "Select passkey" msgstr "Passkey auswählen" +#: apps/remix/app/components/forms/document-preferences-form.tsx +msgid "Select signature types" +msgstr "" + #: apps/remix/app/components/dialogs/organisation-group-create-dialog.tsx msgid "Select the members to add to this group" msgstr "" @@ -5182,10 +5350,6 @@ msgstr "Unterzeichnung" msgid "Signing Certificate" msgstr "Unterzeichnungszertifikat" -#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx -msgid "Signing certificate provided by" -msgstr "Unterzeichnungszertifikat bereitgestellt von" - #: packages/lib/server-only/document/send-completed-email.ts #: packages/lib/server-only/document/send-completed-email.ts msgid "Signing Complete!" @@ -5243,24 +5407,23 @@ msgstr "Einige Unterzeichner haben noch kein Unterschriftsfeld zugewiesen bekomm #: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx #: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.public-profile.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.groups.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.groups.tsx -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/organisation-member-invites-table.tsx #: apps/remix/app/components/tables/organisation-member-invites-table.tsx #: apps/remix/app/components/tables/documents-table-action-dropdown.tsx #: apps/remix/app/components/tables/documents-table-action-button.tsx -#: apps/remix/app/components/general/billing-portal-button.tsx #: apps/remix/app/components/general/billing-plans.tsx -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx #: apps/remix/app/components/general/teams/team-email-usage.tsx #: apps/remix/app/components/general/teams/team-email-dropdown.tsx -#: apps/remix/app/components/general/teams/team-billing-portal-button.tsx #: apps/remix/app/components/general/organisations/organisation-invitations.tsx #: apps/remix/app/components/general/organisations/organisation-invitations.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-portal-button.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx #: apps/remix/app/components/general/document-signing/document-signing-auth-page.tsx #: apps/remix/app/components/general/document-signing/document-signing-auth-account.tsx #: apps/remix/app/components/general/document/document-page-view-dropdown.tsx @@ -5269,7 +5432,6 @@ msgstr "Einige Unterzeichner haben noch kein Unterschriftsfeld zugewiesen bekomm #: apps/remix/app/components/general/document/document-audit-log-download-button.tsx #: apps/remix/app/components/general/direct-template/direct-template-signing-auth-page.tsx #: apps/remix/app/components/general/direct-template/direct-template-page.tsx -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx #: apps/remix/app/components/embed/embed-document-signing-page.tsx #: apps/remix/app/components/embed/embed-direct-template-client-page.tsx #: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx @@ -5279,8 +5441,6 @@ msgstr "Einige Unterzeichner haben noch kein Unterschriftsfeld zugewiesen bekomm #: apps/remix/app/components/dialogs/template-create-dialog.tsx #: apps/remix/app/components/dialogs/team-email-delete-dialog.tsx #: apps/remix/app/components/dialogs/team-email-delete-dialog.tsx -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx #: apps/remix/app/components/dialogs/organisation-member-invite-dialog.tsx #: apps/remix/app/components/dialogs/document-resend-dialog.tsx #: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx @@ -5347,9 +5507,8 @@ msgid "Stats" msgstr "Statistiken" #: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx #: apps/remix/app/components/tables/documents-table.tsx #: apps/remix/app/components/general/template/template-page-view-documents-table.tsx msgid "Status" @@ -5359,6 +5518,18 @@ msgstr "Status" msgid "Step <0>{step} of {maxStep}" msgstr "Schritt <0>{step} von {maxStep}" +#: apps/remix/app/components/tables/admin-organisations-table.tsx +msgid "Stripe" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Stripe customer created successfully" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Stripe Customer ID" +msgstr "" + #: packages/ui/primitives/template-flow/add-template-settings.tsx #: packages/ui/primitives/document-flow/add-subject.tsx msgid "Subject <0>(Optional)" @@ -5368,17 +5539,32 @@ msgstr "Betreff <0>(Optional)" msgid "Subscribe" msgstr "" -#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Subscription" msgstr "Abonnement" -#: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx -msgid "Subscriptions" -msgstr "Abonnements" +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Subscription claim created successfully." +msgstr "" + +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "Subscription claim deleted successfully." +msgstr "" + +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Subscription claim updated successfully." +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/claims.tsx +msgid "Subscription Claims" +msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/organisation-member-invites-table.tsx @@ -5440,8 +5626,8 @@ msgid "System Theme" msgstr "Systemthema" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/tables/organisation-teams-table.tsx -#: apps/remix/app/components/tables/organisation-pending-teams-table.tsx msgid "Team" msgstr "Team" @@ -5458,9 +5644,10 @@ msgstr "" msgid "Team Assignments" msgstr "" -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "Team checkout" -msgstr "Teameinkaufs-Prüfung" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Team Count" +msgstr "" #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx @@ -5570,6 +5757,10 @@ msgstr "Team-Einstellungen" msgid "Team templates" msgstr "Teamvorlagen" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Team url" +msgstr "" + #: apps/remix/app/components/forms/team-update-form.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx msgid "Team URL" @@ -5587,7 +5778,7 @@ msgstr "Teams" msgid "Teams help you organize your work and collaborate with others. Create your first team to get started." msgstr "" -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Teams restricted" msgstr "Teams beschränkt" @@ -5764,6 +5955,12 @@ msgstr "" msgid "The organisation role that will be applied to all members in this group." msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "" +"The organisation you are looking for may have been removed, renamed or may have never\n" +" existed." +msgstr "" + #: apps/remix/app/routes/_authenticated+/_layout.tsx msgid "" "The organisation you are looking for may have been removed, renamed or may have never\n" @@ -5936,6 +6133,10 @@ msgstr "Diese Aktion ist umkehrbar, jedoch bitte seien Sie vorsichtig, da das Ko msgid "This can be overriden by setting the authentication requirements directly on each recipient in the next step." msgstr "Dies kann überschrieben werden, indem die Authentifizierungsanforderungen im nächsten Schritt direkt für jeden Empfänger festgelegt werden." +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "This claim is locked and cannot be deleted." +msgstr "" + #: packages/email/template-components/template-document-super-delete.tsx msgid "This document can not be recovered, if you would like to dispute the reason for future documents please contact support." msgstr "Dieses Dokument kann nicht wiederhergestellt werden. Wenn du den Grund für zukünftige Dokumente anfechten möchtest, kontaktiere bitte den Support." @@ -6020,6 +6221,10 @@ msgstr "Dieses Feld kann nicht geändert oder gelöscht werden. Wenn Sie den dir msgid "This is how the document will reach the recipients once the document is ready for signing." msgstr "So wird das Dokument die Empfänger erreichen, sobald es zum Unterschreiben bereit ist." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "This is the claim that this organisation was initially created with. Any feature flag changes to this claim will be backported into this organisation." +msgstr "" + #: apps/remix/app/routes/_unauthenticated+/team.verify.transfer.token.tsx msgid "This link is invalid or has expired." msgstr "" @@ -6040,10 +6245,6 @@ msgstr "Dieser Zugangsschlüssel wurde bereits registriert." msgid "This passkey is not configured for this application. Please login and add one in the user settings." msgstr "Dieser Passkey ist für diese Anwendung nicht konfiguriert. Bitte melden Sie sich an und fügen Sie einen in den Benutzereinstellungen hinzu." -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "This price includes minimum 5 seats." -msgstr "Dieser Preis beinhaltet mindestens 5 Plätze." - #: packages/ui/primitives/document-flow/add-fields.tsx msgid "This recipient can no longer be modified as they have signed a field, or completed the document." msgstr "Dieser Empfänger kann nicht mehr bearbeitet werden, da er ein Feld unterschrieben oder das Dokument abgeschlossen hat." @@ -6075,14 +6276,9 @@ msgstr "Dieser Token ist ungültig oder abgelaufen. Bitte kontaktieren Sie Ihr T #: apps/remix/app/components/forms/team-update-form.tsx #: apps/remix/app/components/forms/organisation-update-form.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx -#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "This URL is already in use." msgstr "Diese URL wird bereits verwendet." -#: apps/remix/app/components/forms/signup.tsx -msgid "This username has already been taken" -msgstr "Dieser Benutzername ist bereits vergeben" - #: packages/ui/components/document/document-email-checkboxes.tsx msgid "This will be sent to all recipients if a pending document has been deleted." msgstr "Dies wird an alle Empfänger gesendet, wenn ein ausstehendes Dokument gelöscht wurde." @@ -6095,6 +6291,10 @@ msgstr "Dies wird an alle Empfänger gesendet, sobald das Dokument vollständig msgid "This will be sent to the document owner once the document has been fully completed." msgstr "Dies wird an den Dokumenteneigentümer gesendet, sobald das Dokument vollständig abgeschlossen wurde." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "This will ONLY backport feature flags which are set to true, anything disabled in the initial claim will not be backported" +msgstr "" + #: packages/ui/components/recipient/recipient-action-auth-select.tsx msgid "This will override any global settings." msgstr "Dies überschreibt alle globalen Einstellungen." @@ -6385,20 +6585,27 @@ msgstr "Unvollendet" msgid "Unknown" msgstr "Unbekannt" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx -msgid "Unpaid" -msgstr "Unbezahlt" +#: apps/remix/app/components/tables/admin-claims-table.tsx +msgid "Unlimited" +msgstr "" + +#: packages/lib/types/subscription.ts +msgid "Unlimited documents, API and more" +msgstr "" #: apps/remix/app/components/dialogs/team-group-create-dialog.tsx msgid "Untitled Group" msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx #: apps/remix/app/components/forms/public-profile-form.tsx #: apps/remix/app/components/forms/document-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx #: apps/remix/app/components/dialogs/team-member-update-dialog.tsx #: apps/remix/app/components/dialogs/team-group-update-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx @@ -6413,6 +6620,14 @@ msgstr "Aktualisieren" msgid "Update Banner" msgstr "Banner aktualisieren" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx +msgid "Update Billing" +msgstr "" + +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Update Claim" +msgstr "" + #: apps/remix/app/components/forms/organisation-update-form.tsx msgid "Update organisation" msgstr "" @@ -6445,6 +6660,10 @@ msgstr "Empfänger aktualisieren" msgid "Update role" msgstr "Rolle aktualisieren" +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Update Subscription Claim" +msgstr "" + #: apps/remix/app/components/forms/team-update-form.tsx msgid "Update team" msgstr "Team aktualisieren" @@ -6524,7 +6743,7 @@ msgstr "Signatur hochladen" msgid "Upload Template Document" msgstr "Vorlagendokument hochladen" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Upload your brand logo (max 5MB, JPG, PNG, or WebP)" msgstr "Laden Sie Ihr Markenlogo hoch (max. 5MB, JPG, PNG oder WebP)" @@ -6572,10 +6791,6 @@ msgstr "Benutzer" msgid "User has no password." msgstr "Benutzer hat kein Passwort." -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx -msgid "User ID" -msgstr "Benutzer-ID" - #: apps/remix/app/components/dialogs/admin-user-enable-dialog.tsx #: apps/remix/app/components/dialogs/admin-user-disable-dialog.tsx #: apps/remix/app/components/dialogs/admin-user-delete-dialog.tsx @@ -6590,10 +6805,6 @@ msgstr "Benutzerprofile sind hier!" msgid "User with this email already exists. Please use a different email address." msgstr "Ein Benutzer mit dieser E-Mail existiert bereits. Bitte verwenden Sie eine andere E-Mail-Adresse." -#: apps/remix/app/components/forms/signup.tsx -msgid "Username can only container alphanumeric characters and dashes." -msgstr "Der Benutzername darf nur alphanumerische Zeichen und Bindestriche enthalten." - #: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx msgid "Users" msgstr "Benutzer" @@ -6641,7 +6852,7 @@ msgstr "Überprüfen Sie Ihre Team-E-Mail-Adresse" msgid "Version History" msgstr "Versionsverlauf" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx #: apps/remix/app/components/tables/inbox-table.tsx #: apps/remix/app/components/tables/documents-table-action-dropdown.tsx #: apps/remix/app/components/tables/documents-table-action-button.tsx @@ -6709,6 +6920,10 @@ msgstr "Mehr anzeigen" msgid "View Original Document" msgstr "Originaldokument ansehen" +#: apps/remix/app/components/tables/admin-organisations-table.tsx +msgid "View owner" +msgstr "" + #: packages/email/template-components/template-document-self-signed.tsx msgid "View plans" msgstr "Pläne anzeigen" @@ -6778,9 +6993,8 @@ msgstr "Möchten Sie Ihr eigenes öffentliches Profil haben?" msgid "Warning: Assistant as last signer" msgstr "" -#: apps/remix/app/components/general/billing-portal-button.tsx -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx -#: apps/remix/app/components/general/teams/team-billing-portal-button.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-portal-button.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "We are unable to proceed to the billing portal at this time. Please try again, or contact support." msgstr "Wir können zurzeit nicht auf das Abrechnungsportal zugreifen. Bitte versuchen Sie es erneut oder wenden Sie sich an den Support." @@ -6792,10 +7006,19 @@ msgstr "Wir können diesen Schlüssel im Moment nicht entfernen. Bitte versuchen msgid "We are unable to update this passkey at the moment. Please try again later." msgstr "Wir können diesen Schlüssel im Moment nicht aktualisieren. Bitte versuchen Sie es später erneut." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "We couldn't create a Stripe customer. Please try again." +msgstr "" + #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx msgid "We couldn't update the group. Please try again." msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "We couldn't update the organisation. Please try again." +msgstr "" + #: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx msgid "We encountered an error while removing the direct template link. Please try again later." msgstr "Wir sind auf einen Fehler gestoßen, während wir den direkten Vorlagenlink entfernt haben. Bitte versuchen Sie es später erneut." @@ -6829,10 +7052,6 @@ msgstr "Wir sind auf einen unbekannten Fehler gestoßen, während wir versucht h msgid "We encountered an unknown error while attempting to delete it. Please try again later." msgstr "Wir sind auf einen unbekannten Fehler gestoßen, während wir versucht haben, es zu löschen. Bitte versuchen Sie es später erneut." -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx -msgid "We encountered an unknown error while attempting to delete the pending team. Please try again later." -msgstr "Wir sind auf einen unbekannten Fehler gestoßen, während wir versucht haben, das ausstehende Team zu löschen. Bitte versuchen Sie es später erneut." - #: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx msgid "We encountered an unknown error while attempting to delete this organisation. Please try again later." msgstr "" @@ -6943,10 +7162,6 @@ msgstr "Wir haben einen unbekannten Fehler festgestellt, während wir versuchten msgid "We have sent a confirmation email for verification." msgstr "Wir haben eine Bestätigungs-E-Mail zur Überprüfung gesendet." -#: apps/remix/app/components/forms/signup.tsx -msgid "We need a username to create your profile" -msgstr "Wir benötigen einen Benutzernamen, um Ihr Profil zu erstellen" - #: apps/remix/app/components/forms/signup.tsx msgid "We need your signature to sign documents" msgstr "Wir benötigen Ihre Unterschrift, um Dokumente zu unterschreiben" @@ -6959,10 +7174,6 @@ msgstr "Wir konnten das Token nicht in Ihre Zwischenablage kopieren. Bitte versu msgid "We were unable to copy your recovery code to your clipboard. Please try again." msgstr "Wir konnten Ihren Wiederherstellungscode nicht in Ihre Zwischenablage kopieren. Bitte versuchen Sie es erneut." -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "We were unable to create a checkout session. Please try again, or contact support" -msgstr "Wir konnten keine Checkout-Sitzung erstellen. Bitte versuchen Sie es erneut oder kontaktieren Sie den Support" - #: apps/remix/app/components/forms/signup.tsx msgid "We were unable to create your account. Please review the information you provided and try again." msgstr "Wir konnten Ihr Konto nicht erstellen. Bitte überprüfen Sie die von Ihnen angegebenen Informationen und versuchen Sie es erneut." @@ -6992,7 +7203,7 @@ msgstr "Wir konnten die Zwei-Faktor-Authentifizierung für Ihr Konto nicht einri msgid "We were unable to submit this document at this time. Please try again later." msgstr "Wir konnten dieses Dokument zurzeit nicht einreichen. Bitte versuchen Sie es später erneut." -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx msgid "We were unable to update your branding preferences at this time, please try again later" msgstr "Wir konnten Ihre Markenpräferenzen zu diesem Zeitpunkt nicht aktualisieren, bitte versuchen Sie es später noch einmal" @@ -7065,10 +7276,6 @@ msgstr "Webhook-URL" msgid "Webhooks" msgstr "Webhooks" -#: apps/remix/app/components/general/billing-plans.tsx -msgid "Weekly" -msgstr "" - #: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx msgid "Welcome" msgstr "Willkommen" @@ -7114,6 +7321,10 @@ msgstr "Wenn Sie unsere Plattform nutzen, um Ihre elektronische Unterschrift auf msgid "While waiting for them to do so you can create your own Documenso account and get started with document signing right away." msgstr "Während Sie darauf warten, können Sie Ihr eigenes Documenso-Konto erstellen und sofort mit der Dokumentenunterzeichnung beginnen." +#: packages/lib/types/subscription.ts +msgid "Whitelabeling, unlimited members and more" +msgstr "" + #: apps/remix/app/components/dialogs/document-resend-dialog.tsx msgid "Who do you want to remind?" msgstr "Wen möchten Sie erinnern?" @@ -7131,6 +7342,7 @@ msgid "Write about yourself" msgstr "Schreiben Sie über sich selbst" #: apps/remix/app/components/general/billing-plans.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Yearly" msgstr "Jährlich" @@ -7200,6 +7412,10 @@ msgstr "Sie stehen kurz davor, den Zugriff für das Team <0>{0} ({1}) zu wid msgid "You are about to send this document to the recipients. Are you sure you want to continue?" msgstr "Sie sind dabei, dieses Dokument an die Empfänger zu senden. Sind Sie sicher, dass Sie fortfahren möchten?" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "You are currently on the <0>Free Plan." +msgstr "" + #: apps/remix/app/components/dialogs/team-member-update-dialog.tsx msgid "You are currently updating <0>{memberName}." msgstr "" @@ -7306,12 +7522,12 @@ msgstr "Sie können derzeit keine Dokumente hochladen." msgid "You cannot upload encrypted PDFs" msgstr "Sie können keine verschlüsselten PDFs hochladen" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx -msgid "You currently have no access to any teams within this organisation. Please contact your organisation to request access." +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "You currently have an inactive <0>{currentProductName} subscription" msgstr "" -#: apps/remix/app/components/general/billing-portal-button.tsx -msgid "You do not currently have a customer record, this should not happen. Please contact support for assistance." +#: apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx +msgid "You currently have no access to any teams within this organisation. Please contact your organisation to request access." msgstr "" #: apps/remix/app/components/forms/token.tsx @@ -7346,10 +7562,6 @@ msgstr "Du wurdest eingeladen, {0} auf Documenso beizutreten" msgid "You have been invited to join the following organisation" msgstr "" -#: packages/email/templates/organisation-invite.tsx -msgid "You have been invited to join the following team" -msgstr "Du wurdest eingeladen, dem folgenden Team beizutreten" - #: packages/lib/server-only/recipient/set-document-recipients.ts #: packages/lib/server-only/recipient/delete-document-recipient.ts msgid "You have been removed from a document" @@ -7383,6 +7595,10 @@ msgstr "Sie haben noch keine Dokumente erstellt oder erhalten. Bitte laden Sie e msgid "You have reached the maximum limit of {0} direct templates. <0>Upgrade your account to continue!" msgstr "Sie haben das maximale Limit von {0} direkten Vorlagen erreicht. <0>Upgrade your account to continue!" +#: apps/remix/app/components/dialogs/team-create-dialog.tsx +msgid "You have reached the maximum number of teams for your plan. Please contact sales at <0>support@documenso.com if you would like to adjust your plan." +msgstr "" + #: apps/remix/app/components/general/document/document-upload.tsx msgid "You have reached your document limit for this month. Please upgrade your plan." msgstr "Sie haben Ihr Dokumentenlimit für diesen Monat erreicht. Bitte aktualisieren Sie Ihren Plan." @@ -7482,10 +7698,6 @@ msgstr "Sie müssen angemeldet sein, um diese Seite anzuzeigen." msgid "You need to setup 2FA to mark this document as viewed." msgstr "Sie müssen 2FA einrichten, um dieses Dokument als angesehen zu markieren." -#: apps/remix/app/components/forms/signup.tsx -msgid "You will get notified & be able to set up your documenso public profile when we launch the feature." -msgstr "Sie werden benachrichtigt und können Ihr Documenso öffentliches Profil einrichten, wenn wir die Funktion starten." - #: apps/remix/app/components/forms/2fa/enable-authenticator-app-dialog.tsx msgid "You will now be required to enter a code from your authenticator app when signing in." msgstr "Sie müssen bei der Anmeldung jetzt einen Code von Ihrer Authenticator-App eingeben." @@ -7506,11 +7718,11 @@ msgstr "Ihr Avatar wurde erfolgreich aktualisiert." msgid "Your banner has been updated successfully." msgstr "Ihr Banner wurde erfolgreich aktualisiert." -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Your brand website URL" msgstr "Ihre Marken-Website-URL" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx msgid "Your branding preferences have been updated" msgstr "Ihre Markenpräferenzen wurden aktualisiert" @@ -7522,6 +7734,18 @@ msgstr "Ihr Massenversand wurde gestartet. Sie erhalten eine E-Mail-Benachrichti msgid "Your bulk send operation for template \"{templateName}\" has completed." msgstr "Ihre Massenversandoperation für Vorlage \"{templateName}\" ist abgeschlossen." +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "Your current {currentProductName} plan is past due. Please update your payment information." +msgstr "" + +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "Your current plan is inactive." +msgstr "" + +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "Your current plan is past due." +msgstr "" + #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx msgid "Your direct signing templates" msgstr "Ihre direkten Unterzeichnungsvorlagen" @@ -7611,7 +7835,7 @@ msgstr "Ihr Passwort wurde erfolgreich aktualisiert." msgid "Your password has been updated." msgstr "Dein Passwort wurde aktualisiert." -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Your payment for teams is overdue. Please settle the payment to avoid any service disruptions." msgstr "Ihre Zahlung für Teams ist überfällig. Bitte begleichen Sie die Zahlung, um Unterbrechungen des Dienstes zu vermeiden." diff --git a/packages/lib/translations/en/web.po b/packages/lib/translations/en/web.po index d97485dc0..1adab8b7b 100644 --- a/packages/lib/translations/en/web.po +++ b/packages/lib/translations/en/web.po @@ -68,11 +68,6 @@ msgstr "{0, plural, one {# character over the limit} other {# characters over th msgid "{0, plural, one {# recipient} other {# recipients}}" msgstr "{0, plural, one {# recipient} other {# recipients}}" -#. placeholder {0}: row.original.quantity -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx -msgid "{0, plural, one {# Seat} other {# Seats}}" -msgstr "{0, plural, one {# Seat} other {# Seats}}" - #. placeholder {0}: org.teams.length #: apps/remix/app/routes/_authenticated+/dashboard.tsx msgid "{0, plural, one {# team} other {# teams}}" @@ -127,16 +122,6 @@ msgstr "{0} has invited you to {recipientActionVerb} the document \"{1}\"." msgid "{0} invited you to {recipientActionVerb} a document" msgstr "{0} invited you to {recipientActionVerb} a document" -#. placeholder {0}: memberName || memberEmail -#: packages/email/templates/team-join.tsx -msgid "{0} joined the team {teamName} on Documenso" -msgstr "{0} joined the team {teamName} on Documenso" - -#. placeholder {0}: memberName || memberEmail -#: packages/email/templates/team-leave.tsx -msgid "{0} left the team {teamName} on Documenso" -msgstr "{0} left the team {teamName} on Documenso" - #. placeholder {0}: remaining.documents #. placeholder {1}: quota.documents #: apps/remix/app/components/general/document/document-upload.tsx @@ -218,14 +203,6 @@ msgstr "{inviterName} on behalf of \"{teamName}\" has invited you to {0}<0/>\"{d msgid "{inviterName} on behalf of \"{teamName}\" has invited you to {action} {documentName}" msgstr "{inviterName} on behalf of \"{teamName}\" has invited you to {action} {documentName}" -#: packages/email/templates/team-join.tsx -msgid "{memberEmail} joined the following team" -msgstr "{memberEmail} joined the following team" - -#: packages/email/templates/team-leave.tsx -msgid "{memberEmail} left the following team" -msgstr "{memberEmail} left the following team" - #: packages/lib/utils/document-audit-logs.ts msgid "{prefix} added a field" msgstr "{prefix} added a field" @@ -479,6 +456,14 @@ msgstr "<0>You are about to complete signing \"<1>{documentTitle}\".<2/> msgid "<0>You are about to complete viewing \"<1>{documentTitle}\".<2/> Are you sure?" msgstr "<0>You are about to complete viewing \"<1>{documentTitle}\".<2/> Are you sure?" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "0 Free organisations left" +msgstr "0 Free organisations left" + +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "1 Free organisations left" +msgstr "1 Free organisations left" + #: apps/remix/app/components/forms/token.tsx msgid "1 month" msgstr "1 month" @@ -505,6 +490,7 @@ msgid "404 Organisation group not found" msgstr "404 Organisation group not found" #: apps/remix/app/routes/_authenticated+/_layout.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "404 Organisation not found" msgstr "404 Organisation not found" @@ -517,6 +503,14 @@ msgstr "404 Profile not found" msgid "404 Team not found" msgstr "404 Team not found" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "5 documents a month" +msgstr "5 documents a month" + +#: packages/lib/types/subscription.ts +msgid "5 Documents a month" +msgstr "5 Documents a month" + #: apps/remix/app/components/general/generic-error-layout.tsx msgid "500 Internal Server Error" msgstr "500 Internal Server Error" @@ -561,9 +555,29 @@ msgstr "A field was updated" msgid "A means to print or download documents for your records" msgstr "A means to print or download documents for your records" -#: packages/lib/jobs/definitions/emails/send-team-member-joined-email.handler.ts -msgid "A new member has joined your team" -msgstr "A new member has joined your team" +#: packages/email/templates/organisation-join.tsx +msgid "A member has joined your organisation on Documenso" +msgstr "A member has joined your organisation on Documenso" + +#: packages/lib/jobs/definitions/emails/send-organisation-member-left-email.handler.ts +msgid "A member has left your organisation" +msgstr "A member has left your organisation" + +#: packages/email/templates/organisation-leave.tsx +msgid "A member has left your organisation {organisationName}" +msgstr "A member has left your organisation {organisationName}" + +#: packages/email/templates/organisation-leave.tsx +msgid "A member has left your organisation on Documenso" +msgstr "A member has left your organisation on Documenso" + +#: packages/lib/jobs/definitions/emails/send-organisation-member-joined-email.handler.ts +msgid "A new member has joined your organisation" +msgstr "A new member has joined your organisation" + +#: packages/email/templates/organisation-join.tsx +msgid "A new member has joined your organisation {organisationName}" +msgstr "A new member has joined your organisation {organisationName}" #: apps/remix/app/components/forms/token.tsx msgid "A new token was created successfully." @@ -603,19 +617,6 @@ msgstr "A secret that will be sent to your URL so you can verify that the reques msgid "A stable internet connection" msgstr "A stable internet connection" -#: packages/email/templates/team-join.tsx -msgid "A team member has joined a team on Documenso" -msgstr "A team member has joined a team on Documenso" - -#. placeholder {0}: team.name -#: packages/lib/jobs/definitions/emails/send-team-member-left-email.handler.ts -msgid "A team member has left {0}" -msgstr "A team member has left {0}" - -#: packages/email/templates/team-leave.tsx -msgid "A team member has left a team on Documenso" -msgstr "A team member has left a team on Documenso" - #: packages/email/templates/team-delete.tsx #: packages/email/templates/team-delete.tsx msgid "A team you were a part of has been deleted" @@ -625,8 +626,11 @@ msgstr "A team you were a part of has been deleted" msgid "A unique URL to access your profile" msgstr "A unique URL to access your profile" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "A unique URL to identify the organisation" +msgstr "A unique URL to identify the organisation" + #: apps/remix/app/components/forms/organisation-update-form.tsx -#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "A unique URL to identify your organisation" msgstr "A unique URL to identify your organisation" @@ -646,8 +650,8 @@ msgid "Accept" msgstr "Accept" #: packages/email/templates/organisation-invite.tsx -msgid "Accept invitation to join a team on Documenso" -msgstr "Accept invitation to join a team on Documenso" +msgid "Accept invitation to join an organisation on Documenso" +msgstr "Accept invitation to join an organisation on Documenso" #: packages/email/templates/confirm-team-email.tsx msgid "Accept team email request for {teamName} on Documenso" @@ -715,11 +719,12 @@ msgstr "Action" #: apps/remix/app/components/tables/organisation-groups-table.tsx #: apps/remix/app/components/tables/inbox-table.tsx #: apps/remix/app/components/tables/documents-table.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx #: apps/remix/app/components/general/template/template-page-view-documents-table.tsx msgid "Actions" msgstr "Actions" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.teams.tsx #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.members.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx msgid "Active" @@ -860,11 +865,7 @@ msgstr "Add the people who will sign the document." msgid "Add the recipients to create the document with" msgstr "Add the recipients to create the document with" -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "Adding and removing seats will adjust your invoice accordingly." -msgstr "Adding and removing seats will adjust your invoice accordingly." - -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Additional brand information to display at the bottom of emails" msgstr "Additional brand information to display at the bottom of emails" @@ -881,6 +882,10 @@ msgstr "Admin Actions" msgid "Admin panel" msgstr "Admin panel" +#: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx +msgid "Admin Panel" +msgstr "Admin Panel" + #: packages/ui/primitives/template-flow/add-template-settings.tsx #: packages/ui/primitives/document-flow/add-settings.tsx msgid "Advanced Options" @@ -957,6 +962,10 @@ msgstr "Allow signers to dictate next signer" msgid "Allowed Signature Types" msgstr "Allowed Signature Types" +#: apps/remix/app/components/tables/admin-claims-table.tsx +msgid "Allowed teams" +msgstr "Allowed teams" + #: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx msgid "Allows authenticating using biometrics, password managers, hardware keys, etc." msgstr "Allows authenticating using biometrics, password managers, hardware keys, etc." @@ -965,7 +974,7 @@ msgstr "Allows authenticating using biometrics, password managers, hardware keys msgid "Already have an account? <0>Sign in instead" msgstr "Already have an account? <0>Sign in instead" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx msgid "Amount" msgstr "Amount" @@ -986,6 +995,8 @@ msgid "An email containing an invitation will be sent to each member." msgstr "An email containing an invitation will be sent to each member." #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/general/claim-account.tsx #: apps/remix/app/components/forms/token.tsx #: apps/remix/app/components/forms/signup.tsx @@ -1225,6 +1236,10 @@ msgstr "Approving" msgid "Are you sure you want to complete the document? This action cannot be undone. Please ensure that you have completed prefilling all relevant fields before proceeding." msgstr "Are you sure you want to complete the document? This action cannot be undone. Please ensure that you have completed prefilling all relevant fields before proceeding." +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "Are you sure you want to delete the following claim?" +msgstr "Are you sure you want to delete the following claim?" + #: apps/remix/app/components/dialogs/token-delete-dialog.tsx msgid "Are you sure you want to delete this token?" msgstr "Are you sure you want to delete this token?" @@ -1335,9 +1350,9 @@ msgid "Awaiting email confirmation" msgstr "Awaiting email confirmation" #: apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx -#: apps/remix/app/components/forms/signup.tsx #: apps/remix/app/components/dialogs/team-member-create-dialog.tsx #: apps/remix/app/components/dialogs/team-group-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Back" msgstr "Back" @@ -1362,29 +1377,17 @@ msgstr "Backup codes" msgid "Banner Updated" msgstr "Banner Updated" -#: apps/remix/app/components/forms/signup.tsx -msgid "Basic details" -msgstr "Basic details" - #: packages/email/template-components/template-confirmation-email.tsx msgid "Before you get started, please confirm your email address by clicking the button below:" msgstr "Before you get started, please confirm your email address by clicking the button below:" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings._layout.tsx -#: apps/remix/app/routes/_authenticated+/settings+/billing.tsx -#: apps/remix/app/components/general/settings-nav-mobile.tsx -#: apps/remix/app/components/general/settings-nav-desktop.tsx #: apps/remix/app/components/general/teams/team-settings-nav-mobile.tsx #: apps/remix/app/components/general/teams/team-settings-nav-desktop.tsx msgid "Billing" msgstr "Billing" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx -#: apps/remix/app/routes/_authenticated+/settings+/billing.tsx -msgid "Billing has been moved to organisations" -msgstr "Billing has been moved to organisations" - #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx msgid "Black" msgstr "Black" @@ -1393,12 +1396,13 @@ msgstr "Black" msgid "Blue" msgstr "Blue" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx msgid "Branding Preferences" msgstr "Branding Preferences" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx msgid "Branding preferences updated" msgstr "Branding preferences updated" @@ -1507,7 +1511,7 @@ msgstr "Can prepare" #: apps/remix/app/components/dialogs/team-email-add-dialog.tsx #: apps/remix/app/components/dialogs/team-delete-dialog.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx +#: apps/remix/app/components/dialogs/team-create-dialog.tsx #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx #: apps/remix/app/components/dialogs/organisation-member-update-dialog.tsx @@ -1518,9 +1522,13 @@ msgstr "Can prepare" #: apps/remix/app/components/dialogs/organisation-group-create-dialog.tsx #: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx #: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx #: apps/remix/app/components/dialogs/document-resend-dialog.tsx #: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx #: apps/remix/app/components/dialogs/document-delete-dialog.tsx +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx #: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx #: packages/ui/primitives/signature-pad/signature-pad-dialog.tsx #: packages/ui/primitives/document-flow/send-document-action-dialog.tsx @@ -1569,7 +1577,7 @@ msgstr "Checkbox" msgid "Checkbox values" msgstr "Checkbox values" -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Checkout" msgstr "Checkout" @@ -1593,13 +1601,9 @@ msgstr "Choose..." msgid "Claim account" msgstr "Claim account" -#: apps/remix/app/components/forms/signup.tsx -msgid "Claim username" -msgstr "Claim username" - -#: apps/remix/app/components/forms/signup.tsx -msgid "Claim your username now" -msgstr "Claim your username now" +#: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx +msgid "Claims" +msgstr "Claims" #: apps/remix/app/components/dialogs/template-use-dialog.tsx msgid "Clear file" @@ -1655,6 +1659,10 @@ msgstr "Click to insert field" msgid "Close" msgstr "Close" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Compare all plans and features in detail" +msgstr "Compare all plans and features in detail" + #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx #: apps/remix/app/components/forms/signup.tsx @@ -1777,6 +1785,10 @@ msgstr "Consent to Electronic Transactions" msgid "Contact Information" msgstr "Contact Information" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Contact sales here" +msgstr "Contact sales here" + #: apps/remix/app/routes/_authenticated+/admin+/site-settings.tsx msgid "Content" msgstr "Content" @@ -1788,6 +1800,7 @@ msgstr "Content" #: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx #: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx #: packages/ui/primitives/document-flow/document-flow-root.tsx msgid "Continue" @@ -1887,6 +1900,7 @@ msgstr "Copy token" #: apps/remix/app/routes/_profile+/_layout.tsx #: apps/remix/app/components/dialogs/webhook-create-dialog.tsx #: apps/remix/app/components/dialogs/organisation-group-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Create" msgstr "Create" @@ -1923,6 +1937,14 @@ msgstr "Create as draft" msgid "Create as pending" msgstr "Create as pending" +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Create claim" +msgstr "Create claim" + +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Create Claim" +msgstr "Create Claim" + #: apps/remix/app/components/dialogs/template-direct-link-dialog-wrapper.tsx msgid "Create Direct Link" msgstr "Create Direct Link" @@ -1957,18 +1979,26 @@ msgstr "Create one automatically" msgid "Create organisation" msgstr "Create organisation" -#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +#: apps/remix/app/components/general/menu-switcher.tsx msgid "Create Organisation" msgstr "Create Organisation" -#: apps/remix/app/components/general/menu-switcher.tsx -msgid "Create Organization" -msgstr "Create Organization" - #: apps/remix/app/components/dialogs/template-use-dialog.tsx msgid "Create signing links" msgstr "Create signing links" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Create Stripe customer" +msgstr "Create Stripe customer" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Create subscription" +msgstr "Create subscription" + +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Create Subscription Claim" +msgstr "Create Subscription Claim" + #: apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx @@ -2020,7 +2050,7 @@ msgid "Created" msgstr "Created" #: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Created At" msgstr "Created At" @@ -2029,7 +2059,6 @@ msgid "Created by" msgstr "Created by" #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx -#: apps/remix/app/components/tables/organisation-pending-teams-table.tsx msgid "Created on" msgstr "Created on" @@ -2060,14 +2089,18 @@ msgstr "Current recipients:" msgid "Currently all organisation members can access this team" msgstr "Currently all organisation members can access this team" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx +msgid "Currently branding can only be configured for Teams and above plans." +msgstr "Currently branding can only be configured for Teams and above plans." + +#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx +msgid "Currently branding can only be configured on the organisation level." +msgstr "Currently branding can only be configured on the organisation level." + #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups._index.tsx msgid "Custom Organisation Groups" msgstr "Custom Organisation Groups" -#: apps/remix/app/components/general/billing-plans.tsx -msgid "Daily" -msgstr "Daily" - #: apps/remix/app/components/general/app-command-menu.tsx msgid "Dark Mode" msgstr "Dark Mode" @@ -2125,16 +2158,20 @@ msgstr "delete" #: apps/remix/app/components/tables/organisation-teams-table.tsx #: apps/remix/app/components/tables/organisation-groups-table.tsx #: apps/remix/app/components/tables/documents-table-action-dropdown.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx #: apps/remix/app/components/general/document/document-page-view-dropdown.tsx #: apps/remix/app/components/dialogs/webhook-delete-dialog.tsx #: apps/remix/app/components/dialogs/token-delete-dialog.tsx #: apps/remix/app/components/dialogs/template-delete-dialog.tsx #: apps/remix/app/components/dialogs/team-group-delete-dialog.tsx #: apps/remix/app/components/dialogs/team-delete-dialog.tsx +#: apps/remix/app/components/dialogs/team-delete-dialog.tsx #: apps/remix/app/components/dialogs/organisation-member-delete-dialog.tsx #: apps/remix/app/components/dialogs/organisation-group-delete-dialog.tsx #: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx #: apps/remix/app/components/dialogs/document-delete-dialog.tsx +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx msgid "Delete" msgstr "Delete" @@ -2174,7 +2211,6 @@ msgid "Delete Document" msgstr "Delete Document" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.general.tsx -#: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx msgid "Delete organisation" msgstr "Delete organisation" @@ -2190,8 +2226,11 @@ msgstr "Delete organisation member" msgid "Delete passkey" msgstr "Delete passkey" +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "Delete Subscription Claim" +msgstr "Delete Subscription Claim" + #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx -#: apps/remix/app/components/dialogs/team-delete-dialog.tsx msgid "Delete team" msgstr "Delete team" @@ -2615,7 +2654,7 @@ msgstr "Documents Viewed" msgid "Don't have an account? <0>Sign up" msgstr "Don't have an account? <0>Sign up" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx #: apps/remix/app/components/tables/documents-table-action-dropdown.tsx #: apps/remix/app/components/tables/documents-table-action-button.tsx #: apps/remix/app/components/general/document/document-page-view-dropdown.tsx @@ -2670,7 +2709,7 @@ msgstr "Dropdown" msgid "Dropdown options" msgstr "Dropdown options" -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Due to an unpaid invoice, your team has been restricted. Please settle the payment to restore full access to your team." msgstr "Due to an unpaid invoice, your team has been restricted. Please settle the payment to restore full access to your team." @@ -2713,6 +2752,7 @@ msgstr "Electronic Signature Disclosure" #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-dashboard-users-table.tsx #: apps/remix/app/components/general/document-signing/document-signing-email-field.tsx @@ -2785,6 +2825,10 @@ msgstr "Email verification has been removed" msgid "Email verification has been resent" msgstr "Email verification has been resent" +#: packages/lib/types/subscription.ts +msgid "Embedding, 5 members included and more" +msgstr "Embedding, 5 members included and more" + #: packages/ui/primitives/document-flow/add-fields.tsx msgid "Empty field" msgstr "Empty field" @@ -2815,7 +2859,7 @@ msgstr "Enable Account" msgid "Enable Authenticator App" msgstr "Enable Authenticator App" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Enable custom branding for all documents in this team." msgstr "Enable custom branding for all documents in this team." @@ -2844,11 +2888,11 @@ msgstr "Enabling the account results in the user being able to use the account a msgid "Enclosed Document" msgstr "Enclosed Document" -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx -msgid "Ends On" -msgstr "Ends On" +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Enter claim name" +msgstr "Enter claim name" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Enter your brand details" msgstr "Enter your brand details" @@ -2868,7 +2912,12 @@ msgstr "Enter your name" msgid "Enter your text here" msgstr "Enter your text here" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Enterprise" +msgstr "Enterprise" + #: apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx #: apps/remix/app/components/general/verify-email-banner.tsx #: apps/remix/app/components/general/template/template-edit-form.tsx @@ -2943,6 +2992,14 @@ msgstr "Expires on {0}" msgid "External ID" msgstr "External ID" +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Failed to create subscription claim." +msgstr "Failed to create subscription claim." + +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "Failed to delete subscription claim." +msgstr "Failed to delete subscription claim." + #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx msgid "Failed to reseal document" msgstr "Failed to reseal document" @@ -2955,6 +3012,10 @@ msgstr "Failed to save settings." msgid "Failed to update recipient" msgstr "Failed to update recipient" +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Failed to update subscription claim." +msgstr "Failed to update subscription claim." + #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx msgid "Failed to update webhook" msgstr "Failed to update webhook" @@ -2963,6 +3024,12 @@ msgstr "Failed to update webhook" msgid "Failed: {failedCount}" msgstr "Failed: {failedCount}" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Feature Flags" +msgstr "Feature Flags" + #: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx msgid "Field character limit" msgstr "Field character limit" @@ -3014,6 +3081,10 @@ msgstr "File cannot be larger than {APP_DOCUMENT_UPLOAD_SIZE_LIMIT}MB" msgid "File size exceeds the limit of {APP_DOCUMENT_UPLOAD_SIZE_LIMIT} MB" msgstr "File size exceeds the limit of {APP_DOCUMENT_UPLOAD_SIZE_LIMIT} MB" +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Fill in the details to create a new subscription claim." +msgstr "Fill in the details to create a new subscription claim." + #: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx #: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx #: packages/ui/primitives/document-flow/field-items-advanced-settings/name-field.tsx @@ -3031,6 +3102,10 @@ msgstr "For any questions regarding this disclosure, electronic signatures, or a msgid "For each recipient, provide their email (required) and name (optional) in separate columns. Download the template CSV below for the correct format." msgstr "For each recipient, provide their email (required) and name (optional) in separate columns. Download the template CSV below for the correct format." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "For example, if the claim has a new flag \"FLAG_1\" set to true, then this organisation will get that flag added." +msgstr "For example, if the claim has a new flag \"FLAG_1\" set to true, then this organisation will get that flag added." + #: packages/lib/server-only/auth/send-forgot-password.ts msgid "Forgot Password?" msgstr "Forgot Password?" @@ -3041,6 +3116,10 @@ msgstr "Forgot Password?" msgid "Forgot your password?" msgstr "Forgot your password?" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Free" +msgstr "Free" + #: packages/ui/primitives/document-flow/types.ts msgid "Free Signature" msgstr "Free Signature" @@ -3074,6 +3153,7 @@ msgid "Global recipient action authentication" msgstr "Global recipient action authentication" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "Go back" msgstr "Go back" @@ -3188,7 +3268,6 @@ msgid "Here you can manage your password and security settings." msgstr "Here you can manage your password and security settings." #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx -#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx msgid "Here you can set preferences and defaults for branding." msgstr "Here you can set preferences and defaults for branding." @@ -3255,6 +3334,14 @@ msgstr "I am the owner of this document" msgid "I'm sure! Delete it" msgstr "I'm sure! Delete it" +#: apps/remix/app/components/tables/admin-claims-table.tsx +msgid "ID" +msgstr "ID" + +#: apps/remix/app/components/tables/admin-claims-table.tsx +msgid "ID copied to clipboard" +msgstr "ID copied to clipboard" + #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx msgid "If you do not want to use the authenticator prompted, you can close it, which will then display the next available authenticator." msgstr "If you do not want to use the authenticator prompted, you can close it, which will then display the next available authenticator." @@ -3293,6 +3380,7 @@ msgstr "Inherit authentication method" #: apps/remix/app/components/forms/document-preferences-form.tsx #: apps/remix/app/components/forms/document-preferences-form.tsx #: apps/remix/app/components/forms/document-preferences-form.tsx +#: apps/remix/app/components/forms/document-preferences-form.tsx msgid "Inherit from organisation" msgstr "Inherit from organisation" @@ -3300,6 +3388,11 @@ msgstr "Inherit from organisation" msgid "Inherit organisation members" msgstr "Inherit organisation members" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Inherited subscription claim" +msgstr "Inherited subscription claim" + #: apps/remix/app/components/general/document-signing/document-signing-initials-field.tsx #: packages/ui/primitives/document-flow/types.ts msgid "Initials" @@ -3380,7 +3473,7 @@ msgstr "Invite team members to collaborate" msgid "Invited At" msgstr "Invited At" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx msgid "Invoice" msgstr "Invoice" @@ -3474,7 +3567,7 @@ msgstr "Last used" msgid "Leaderboard" msgstr "Leaderboard" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/dialogs/organisation-leave-dialog.tsx msgid "Leave" msgstr "Leave" @@ -3536,8 +3629,10 @@ msgstr "Loading..." msgid "Login" msgstr "Login" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/tables/organisation-teams-table.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Manage" msgstr "Manage" @@ -3550,10 +3645,18 @@ msgstr "Manage {0}'s profile" msgid "Manage all organisations you are currently associated with." msgstr "Manage all organisations you are currently associated with." +#: apps/remix/app/routes/_authenticated+/admin+/claims.tsx +msgid "Manage all subscription claims" +msgstr "Manage all subscription claims" + #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/templates.$id._index.tsx msgid "Manage and view template" msgstr "Manage and view template" +#: apps/remix/app/components/general/organisations/organisation-billing-portal-button.tsx +msgid "Manage billing" +msgstr "Manage billing" + #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx msgid "Manage details for this public template" msgstr "Manage details for this public template" @@ -3566,6 +3669,14 @@ msgstr "Manage Direct Link" msgid "Manage documents" msgstr "Manage documents" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Manage organisation" +msgstr "Manage organisation" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx +msgid "Manage organisations" +msgstr "Manage organisations" + #: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx msgid "Manage passkeys" msgstr "Manage passkeys" @@ -3574,17 +3685,20 @@ msgstr "Manage passkeys" msgid "Manage permissions and access controls" msgstr "Manage permissions and access controls" -#: apps/remix/app/components/general/teams/team-billing-portal-button.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "Manage subscription" msgstr "Manage subscription" -#: apps/remix/app/components/general/billing-portal-button.tsx -msgid "Manage Subscription" -msgstr "Manage Subscription" +#. placeholder {0}: organisation.name +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Manage the {0} organisation" +msgstr "Manage the {0} organisation" -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx -msgid "Manage subscriptions" -msgstr "Manage subscriptions" +#. placeholder {1}: organisation.name +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Manage the {1} organisation subscription" +msgstr "Manage the {1} organisation subscription" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups._index.tsx msgid "Manage the custom groups of members for your organisation." @@ -3656,13 +3770,19 @@ msgstr "Max" msgid "Maximum file size: 4MB. Maximum 100 rows per upload. Blank values will use template defaults." msgstr "Maximum file size: 4MB. Maximum 100 rows per upload. Blank values will use template defaults." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/dialogs/team-member-create-dialog.tsx #: packages/lib/constants/teams.ts #: packages/lib/constants/organisations.ts msgid "Member" msgstr "Member" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Member Count" +msgstr "Member Count" + +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/tables/organisation-members-table.tsx msgid "Member Since" msgstr "Member Since" @@ -3693,7 +3813,12 @@ msgstr "Min" msgid "Modify recipients" msgstr "Modify recipients" +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Modify the details of the subscription claim." +msgstr "Modify the details of the subscription claim." + #: apps/remix/app/components/general/billing-plans.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Monthly" msgstr "Monthly" @@ -3711,9 +3836,11 @@ msgstr "Monthly Active Users: Users that had at least one of their documents com #: apps/remix/app/components/tables/admin-leaderboard-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-dashboard-users-table.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx #: apps/remix/app/components/general/claim-account.tsx #: apps/remix/app/components/general/document-signing/document-signing-name-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx @@ -3766,7 +3893,6 @@ msgstr "New Password" msgid "New Template" msgstr "New Template" -#: apps/remix/app/components/forms/signup.tsx #: apps/remix/app/components/embed/embed-document-signing-page.tsx #: apps/remix/app/components/embed/embed-direct-template-client-page.tsx #: apps/remix/app/components/dialogs/team-member-create-dialog.tsx @@ -3841,6 +3967,10 @@ msgstr "No results found." msgid "No signature field found" msgstr "No signature field found" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "No Stripe customer attached" +msgstr "No Stripe customer attached" + #: apps/remix/app/components/tables/team-groups-table.tsx msgid "No team groups found" msgstr "No team groups found" @@ -3868,6 +3998,7 @@ msgstr "No value found." msgid "No worries, it happens! Enter your email and we'll email you a special link to reset your password." msgstr "No worries, it happens! Enter your email and we'll email you a special link to reset your password." +#: apps/remix/app/components/tables/admin-claims-table.tsx #: packages/lib/constants/document.ts msgid "None" msgstr "None" @@ -3892,6 +4023,16 @@ msgstr "Number" msgid "Number format" msgstr "Number format" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Number of members allowed. 0 = Unlimited" +msgstr "Number of members allowed. 0 = Unlimited" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Number of teams allowed. 0 = Unlimited" +msgstr "Number of teams allowed. 0 = Unlimited" + #. placeholder {0}: document.team?.name #: apps/remix/app/components/general/document-signing/document-signing-page-view.tsx msgid "on behalf of \"{0}\" has invited you to approve this document" @@ -3952,10 +4093,6 @@ msgstr "Only admins can access and view the document" msgid "Only managers and above can access and view the document" msgstr "Only managers and above can access and view the document" -#: apps/remix/app/components/forms/signup.tsx -msgid "Only subscribers can have a username shorter than 6 characters" -msgstr "Only subscribers can have a username shorter than 6 characters" - #: apps/remix/app/routes/_profile+/_layout.tsx #: apps/remix/app/components/general/generic-error-layout.tsx #: apps/remix/app/components/general/generic-error-layout.tsx @@ -3975,7 +4112,8 @@ msgstr "Or" msgid "Or continue with" msgstr "Or continue with" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Organisation" msgstr "Organisation" @@ -3991,6 +4129,11 @@ msgstr "Organisation group not found" msgid "Organisation Group Settings" msgstr "Organisation Group Settings" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Organisation has been updated successfully" +msgstr "Organisation has been updated successfully" + #: apps/remix/app/routes/_unauthenticated+/organisation.invite.$token.tsx msgid "Organisation invitation" msgstr "Organisation invitation" @@ -4010,15 +4153,18 @@ msgid "Organisation Member" msgstr "Organisation Member" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.members.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "Organisation Members" msgstr "Organisation Members" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/forms/organisation-update-form.tsx #: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Organisation Name" msgstr "Organisation Name" #: apps/remix/app/routes/_authenticated+/_layout.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "Organisation not found" msgstr "Organisation not found" @@ -4031,6 +4177,7 @@ msgid "Organisation role" msgstr "Organisation role" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/components/dialogs/organisation-member-invite-dialog.tsx msgid "Organisation Role" msgstr "Organisation Role" @@ -4042,13 +4189,18 @@ msgstr "Organisation settings" msgid "Organisation Settings" msgstr "Organisation Settings" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Organisation Teams" +msgstr "Organisation Teams" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/forms/organisation-update-form.tsx -#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Organisation URL" msgstr "Organisation URL" #: apps/remix/app/routes/_authenticated+/dashboard.tsx #: apps/remix/app/routes/_authenticated+/settings+/organisations.tsx +#: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx #: apps/remix/app/components/general/settings-nav-mobile.tsx #: apps/remix/app/components/general/settings-nav-desktop.tsx #: apps/remix/app/components/general/menu-switcher.tsx @@ -4071,8 +4223,9 @@ msgstr "Otherwise, the document will be created as a draft." #: apps/remix/app/routes/_authenticated+/dashboard.tsx #: apps/remix/app/routes/_authenticated+/dashboard.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/tables/organisation-members-table.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Owner" msgstr "Owner" @@ -4087,10 +4240,6 @@ msgstr "Page {0} of {1}" msgid "Page {0} of {numPages}" msgstr "Page {0} of {numPages}" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx -msgid "Paid" -msgstr "Paid" - #: apps/remix/app/components/forms/signin.tsx msgid "Passkey" msgstr "Passkey" @@ -4161,20 +4310,11 @@ msgstr "Password updated" msgid "Password updated!" msgstr "Password updated!" -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx -msgid "Pay" -msgstr "Pay" - -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "Payment is required to finalise the creation of your team." -msgstr "Payment is required to finalise the creation of your team." - -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Payment overdue" msgstr "Payment overdue" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.teams.tsx #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.members.tsx #: apps/remix/app/components/general/template/template-page-view-documents-table.tsx #: apps/remix/app/components/general/document/document-status.tsx @@ -4200,9 +4340,15 @@ msgstr "Pending Documents" msgid "Pending invitations" msgstr "Pending invitations" -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx -msgid "Pending team deleted." -msgstr "Pending team deleted." +#: apps/remix/app/components/general/billing-plans.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "per month" +msgstr "per month" + +#: apps/remix/app/components/general/billing-plans.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "per year" +msgstr "per year" #: apps/remix/app/components/general/menu-switcher.tsx msgid "Personal Account" @@ -4364,6 +4510,10 @@ msgstr "Please type {0} to confirm" msgid "Please type <0>{0} to confirm." msgstr "Please type <0>{0} to confirm." +#: apps/remix/app/components/forms/branding-preferences-form.tsx +msgid "Please upload a logo" +msgstr "Please upload a logo" + #: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx msgid "Pre-formatted CSV template with example data." msgstr "Pre-formatted CSV template with example data." @@ -4430,10 +4580,6 @@ msgstr "Public Profile" msgid "Public profile URL" msgstr "Public profile URL" -#: apps/remix/app/components/forms/signup.tsx -msgid "Public profile username" -msgstr "Public profile username" - #: apps/remix/app/components/tables/templates-table.tsx msgid "Public templates are connected to your public profile. Any modifications to public templates will also appear in your public profile." msgstr "Public templates are connected to your public profile. Any modifications to public templates will also appear in your public profile." @@ -4616,7 +4762,6 @@ msgstr "Reminder: Please {recipientActionVerb} this document" msgid "Reminder: Please {recipientActionVerb} your document" msgstr "Reminder: Please {recipientActionVerb} your document" -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx #: apps/remix/app/components/tables/team-members-table.tsx #: apps/remix/app/components/tables/team-groups-table.tsx #: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx @@ -4625,7 +4770,7 @@ msgstr "Reminder: Please {recipientActionVerb} your document" #: apps/remix/app/components/general/teams/team-email-dropdown.tsx #: apps/remix/app/components/general/document-signing/document-signing-field-container.tsx #: apps/remix/app/components/general/document-signing/document-signing-field-container.tsx -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx #: apps/remix/app/components/forms/avatar-image.tsx #: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx #: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx @@ -4705,11 +4850,11 @@ msgstr "Reset Password" msgid "Resetting Password..." msgstr "Resetting Password..." -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Resolve" msgstr "Resolve" -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Resolve payment" msgstr "Resolve payment" @@ -4749,7 +4894,7 @@ msgstr "Revoke" msgid "Revoke access" msgstr "Revoke access" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/tables/team-members-table.tsx #: apps/remix/app/components/tables/team-groups-table.tsx #: apps/remix/app/components/tables/organisation-members-table.tsx @@ -4759,7 +4904,6 @@ msgstr "Revoke access" #: apps/remix/app/components/dialogs/team-member-update-dialog.tsx #: apps/remix/app/components/dialogs/team-group-update-dialog.tsx #: apps/remix/app/components/dialogs/organisation-member-update-dialog.tsx -#: apps/remix/app/components/dialogs/organisation-member-invite-dialog.tsx msgid "Role" msgstr "Role" @@ -4792,6 +4936,14 @@ msgstr "Save Template" msgid "Search" msgstr "Search" +#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx +msgid "Search and manage all organisations" +msgstr "Search and manage all organisations" + +#: apps/remix/app/routes/_authenticated+/admin+/claims.tsx +msgid "Search by claim ID or name" +msgstr "Search by claim ID or name" + #: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx msgid "Search by document title" msgstr "Search by document title" @@ -4801,6 +4953,10 @@ msgstr "Search by document title" msgid "Search by name or email" msgstr "Search by name or email" +#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx +msgid "Search by organisation ID, name, customer ID or owner email" +msgstr "Search by organisation ID, name, customer ID or owner email" + #: apps/remix/app/components/general/document/document-search.tsx msgid "Search documents..." msgstr "Search documents..." @@ -4829,6 +4985,14 @@ msgstr "Security activity" msgid "Select" msgstr "Select" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Select a plan" +msgstr "Select a plan" + +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Select a plan to continue" +msgstr "Select a plan to continue" + #: apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx msgid "Select a team to view its dashboard" msgstr "Select a team to view its dashboard" @@ -4884,6 +5048,10 @@ msgstr "Select members to add to this team" msgid "Select passkey" msgstr "Select passkey" +#: apps/remix/app/components/forms/document-preferences-form.tsx +msgid "Select signature types" +msgstr "Select signature types" + #: apps/remix/app/components/dialogs/organisation-group-create-dialog.tsx msgid "Select the members to add to this group" msgstr "Select the members to add to this group" @@ -5177,10 +5345,6 @@ msgstr "Signing" msgid "Signing Certificate" msgstr "Signing Certificate" -#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx -msgid "Signing certificate provided by" -msgstr "Signing certificate provided by" - #: packages/lib/server-only/document/send-completed-email.ts #: packages/lib/server-only/document/send-completed-email.ts msgid "Signing Complete!" @@ -5238,24 +5402,23 @@ msgstr "Some signers have not been assigned a signature field. Please assign at #: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx #: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.public-profile.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.groups.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.groups.tsx -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/organisation-member-invites-table.tsx #: apps/remix/app/components/tables/organisation-member-invites-table.tsx #: apps/remix/app/components/tables/documents-table-action-dropdown.tsx #: apps/remix/app/components/tables/documents-table-action-button.tsx -#: apps/remix/app/components/general/billing-portal-button.tsx #: apps/remix/app/components/general/billing-plans.tsx -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx #: apps/remix/app/components/general/teams/team-email-usage.tsx #: apps/remix/app/components/general/teams/team-email-dropdown.tsx -#: apps/remix/app/components/general/teams/team-billing-portal-button.tsx #: apps/remix/app/components/general/organisations/organisation-invitations.tsx #: apps/remix/app/components/general/organisations/organisation-invitations.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-portal-button.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx #: apps/remix/app/components/general/document-signing/document-signing-auth-page.tsx #: apps/remix/app/components/general/document-signing/document-signing-auth-account.tsx #: apps/remix/app/components/general/document/document-page-view-dropdown.tsx @@ -5264,7 +5427,6 @@ msgstr "Some signers have not been assigned a signature field. Please assign at #: apps/remix/app/components/general/document/document-audit-log-download-button.tsx #: apps/remix/app/components/general/direct-template/direct-template-signing-auth-page.tsx #: apps/remix/app/components/general/direct-template/direct-template-page.tsx -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx #: apps/remix/app/components/embed/embed-document-signing-page.tsx #: apps/remix/app/components/embed/embed-direct-template-client-page.tsx #: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx @@ -5274,8 +5436,6 @@ msgstr "Some signers have not been assigned a signature field. Please assign at #: apps/remix/app/components/dialogs/template-create-dialog.tsx #: apps/remix/app/components/dialogs/team-email-delete-dialog.tsx #: apps/remix/app/components/dialogs/team-email-delete-dialog.tsx -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx #: apps/remix/app/components/dialogs/organisation-member-invite-dialog.tsx #: apps/remix/app/components/dialogs/document-resend-dialog.tsx #: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx @@ -5342,9 +5502,8 @@ msgid "Stats" msgstr "Stats" #: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx #: apps/remix/app/components/tables/documents-table.tsx #: apps/remix/app/components/general/template/template-page-view-documents-table.tsx msgid "Status" @@ -5354,6 +5513,18 @@ msgstr "Status" msgid "Step <0>{step} of {maxStep}" msgstr "Step <0>{step} of {maxStep}" +#: apps/remix/app/components/tables/admin-organisations-table.tsx +msgid "Stripe" +msgstr "Stripe" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Stripe customer created successfully" +msgstr "Stripe customer created successfully" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Stripe Customer ID" +msgstr "Stripe Customer ID" + #: packages/ui/primitives/template-flow/add-template-settings.tsx #: packages/ui/primitives/document-flow/add-subject.tsx msgid "Subject <0>(Optional)" @@ -5363,17 +5534,32 @@ msgstr "Subject <0>(Optional)" msgid "Subscribe" msgstr "Subscribe" -#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Subscription" msgstr "Subscription" -#: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx -msgid "Subscriptions" -msgstr "Subscriptions" +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Subscription claim created successfully." +msgstr "Subscription claim created successfully." + +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "Subscription claim deleted successfully." +msgstr "Subscription claim deleted successfully." + +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Subscription claim updated successfully." +msgstr "Subscription claim updated successfully." + +#: apps/remix/app/routes/_authenticated+/admin+/claims.tsx +msgid "Subscription Claims" +msgstr "Subscription Claims" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/organisation-member-invites-table.tsx @@ -5435,8 +5621,8 @@ msgid "System Theme" msgstr "System Theme" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/tables/organisation-teams-table.tsx -#: apps/remix/app/components/tables/organisation-pending-teams-table.tsx msgid "Team" msgstr "Team" @@ -5453,9 +5639,10 @@ msgstr "Team Admin" msgid "Team Assignments" msgstr "Team Assignments" -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "Team checkout" -msgstr "Team checkout" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Team Count" +msgstr "Team Count" #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx @@ -5565,6 +5752,10 @@ msgstr "Team Settings" msgid "Team templates" msgstr "Team templates" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Team url" +msgstr "Team url" + #: apps/remix/app/components/forms/team-update-form.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx msgid "Team URL" @@ -5582,7 +5773,7 @@ msgstr "Teams" msgid "Teams help you organize your work and collaborate with others. Create your first team to get started." msgstr "Teams help you organize your work and collaborate with others. Create your first team to get started." -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Teams restricted" msgstr "Teams restricted" @@ -5761,6 +5952,14 @@ msgstr "" msgid "The organisation role that will be applied to all members in this group." msgstr "The organisation role that will be applied to all members in this group." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "" +"The organisation you are looking for may have been removed, renamed or may have never\n" +" existed." +msgstr "" +"The organisation you are looking for may have been removed, renamed or may have never\n" +" existed." + #: apps/remix/app/routes/_authenticated+/_layout.tsx msgid "" "The organisation you are looking for may have been removed, renamed or may have never\n" @@ -5939,6 +6138,10 @@ msgstr "This action is reversible, but please be careful as the account may be a msgid "This can be overriden by setting the authentication requirements directly on each recipient in the next step." msgstr "This can be overriden by setting the authentication requirements directly on each recipient in the next step." +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "This claim is locked and cannot be deleted." +msgstr "This claim is locked and cannot be deleted." + #: packages/email/template-components/template-document-super-delete.tsx msgid "This document can not be recovered, if you would like to dispute the reason for future documents please contact support." msgstr "This document can not be recovered, if you would like to dispute the reason for future documents please contact support." @@ -6023,6 +6226,10 @@ msgstr "This field cannot be modified or deleted. When you share this template's msgid "This is how the document will reach the recipients once the document is ready for signing." msgstr "This is how the document will reach the recipients once the document is ready for signing." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "This is the claim that this organisation was initially created with. Any feature flag changes to this claim will be backported into this organisation." +msgstr "This is the claim that this organisation was initially created with. Any feature flag changes to this claim will be backported into this organisation." + #: apps/remix/app/routes/_unauthenticated+/team.verify.transfer.token.tsx msgid "This link is invalid or has expired." msgstr "This link is invalid or has expired." @@ -6043,10 +6250,6 @@ msgstr "This passkey has already been registered." msgid "This passkey is not configured for this application. Please login and add one in the user settings." msgstr "This passkey is not configured for this application. Please login and add one in the user settings." -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "This price includes minimum 5 seats." -msgstr "This price includes minimum 5 seats." - #: packages/ui/primitives/document-flow/add-fields.tsx msgid "This recipient can no longer be modified as they have signed a field, or completed the document." msgstr "This recipient can no longer be modified as they have signed a field, or completed the document." @@ -6078,14 +6281,9 @@ msgstr "This token is invalid or has expired. Please contact your team for a new #: apps/remix/app/components/forms/team-update-form.tsx #: apps/remix/app/components/forms/organisation-update-form.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx -#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "This URL is already in use." msgstr "This URL is already in use." -#: apps/remix/app/components/forms/signup.tsx -msgid "This username has already been taken" -msgstr "This username has already been taken" - #: packages/ui/components/document/document-email-checkboxes.tsx msgid "This will be sent to all recipients if a pending document has been deleted." msgstr "This will be sent to all recipients if a pending document has been deleted." @@ -6098,6 +6296,10 @@ msgstr "This will be sent to all recipients once the document has been fully com msgid "This will be sent to the document owner once the document has been fully completed." msgstr "This will be sent to the document owner once the document has been fully completed." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "This will ONLY backport feature flags which are set to true, anything disabled in the initial claim will not be backported" +msgstr "This will ONLY backport feature flags which are set to true, anything disabled in the initial claim will not be backported" + #: packages/ui/components/recipient/recipient-action-auth-select.tsx msgid "This will override any global settings." msgstr "This will override any global settings." @@ -6388,20 +6590,27 @@ msgstr "Uncompleted" msgid "Unknown" msgstr "Unknown" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx -msgid "Unpaid" -msgstr "Unpaid" +#: apps/remix/app/components/tables/admin-claims-table.tsx +msgid "Unlimited" +msgstr "Unlimited" + +#: packages/lib/types/subscription.ts +msgid "Unlimited documents, API and more" +msgstr "Unlimited documents, API and more" #: apps/remix/app/components/dialogs/team-group-create-dialog.tsx msgid "Untitled Group" msgstr "Untitled Group" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx #: apps/remix/app/components/forms/public-profile-form.tsx #: apps/remix/app/components/forms/document-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx #: apps/remix/app/components/dialogs/team-member-update-dialog.tsx #: apps/remix/app/components/dialogs/team-group-update-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx @@ -6416,6 +6625,14 @@ msgstr "Update" msgid "Update Banner" msgstr "Update Banner" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx +msgid "Update Billing" +msgstr "Update Billing" + +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Update Claim" +msgstr "Update Claim" + #: apps/remix/app/components/forms/organisation-update-form.tsx msgid "Update organisation" msgstr "Update organisation" @@ -6448,6 +6665,10 @@ msgstr "Update Recipient" msgid "Update role" msgstr "Update role" +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Update Subscription Claim" +msgstr "Update Subscription Claim" + #: apps/remix/app/components/forms/team-update-form.tsx msgid "Update team" msgstr "Update team" @@ -6527,7 +6748,7 @@ msgstr "Upload Signature" msgid "Upload Template Document" msgstr "Upload Template Document" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Upload your brand logo (max 5MB, JPG, PNG, or WebP)" msgstr "Upload your brand logo (max 5MB, JPG, PNG, or WebP)" @@ -6575,10 +6796,6 @@ msgstr "User" msgid "User has no password." msgstr "User has no password." -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx -msgid "User ID" -msgstr "User ID" - #: apps/remix/app/components/dialogs/admin-user-enable-dialog.tsx #: apps/remix/app/components/dialogs/admin-user-disable-dialog.tsx #: apps/remix/app/components/dialogs/admin-user-delete-dialog.tsx @@ -6593,10 +6810,6 @@ msgstr "User profiles are here!" msgid "User with this email already exists. Please use a different email address." msgstr "User with this email already exists. Please use a different email address." -#: apps/remix/app/components/forms/signup.tsx -msgid "Username can only container alphanumeric characters and dashes." -msgstr "Username can only container alphanumeric characters and dashes." - #: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx msgid "Users" msgstr "Users" @@ -6644,7 +6857,7 @@ msgstr "Verify your team email address" msgid "Version History" msgstr "Version History" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx #: apps/remix/app/components/tables/inbox-table.tsx #: apps/remix/app/components/tables/documents-table-action-dropdown.tsx #: apps/remix/app/components/tables/documents-table-action-button.tsx @@ -6712,6 +6925,10 @@ msgstr "View more" msgid "View Original Document" msgstr "View Original Document" +#: apps/remix/app/components/tables/admin-organisations-table.tsx +msgid "View owner" +msgstr "View owner" + #: packages/email/template-components/template-document-self-signed.tsx msgid "View plans" msgstr "View plans" @@ -6781,9 +6998,8 @@ msgstr "Want your own public profile?" msgid "Warning: Assistant as last signer" msgstr "Warning: Assistant as last signer" -#: apps/remix/app/components/general/billing-portal-button.tsx -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx -#: apps/remix/app/components/general/teams/team-billing-portal-button.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-portal-button.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "We are unable to proceed to the billing portal at this time. Please try again, or contact support." msgstr "We are unable to proceed to the billing portal at this time. Please try again, or contact support." @@ -6795,10 +7011,19 @@ msgstr "We are unable to remove this passkey at the moment. Please try again lat msgid "We are unable to update this passkey at the moment. Please try again later." msgstr "We are unable to update this passkey at the moment. Please try again later." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "We couldn't create a Stripe customer. Please try again." +msgstr "We couldn't create a Stripe customer. Please try again." + #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx msgid "We couldn't update the group. Please try again." msgstr "We couldn't update the group. Please try again." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "We couldn't update the organisation. Please try again." +msgstr "We couldn't update the organisation. Please try again." + #: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx msgid "We encountered an error while removing the direct template link. Please try again later." msgstr "We encountered an error while removing the direct template link. Please try again later." @@ -6832,10 +7057,6 @@ msgstr "We encountered an unknown error while attempting to create a team. Pleas msgid "We encountered an unknown error while attempting to delete it. Please try again later." msgstr "We encountered an unknown error while attempting to delete it. Please try again later." -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx -msgid "We encountered an unknown error while attempting to delete the pending team. Please try again later." -msgstr "We encountered an unknown error while attempting to delete the pending team. Please try again later." - #: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx msgid "We encountered an unknown error while attempting to delete this organisation. Please try again later." msgstr "We encountered an unknown error while attempting to delete this organisation. Please try again later." @@ -6946,10 +7167,6 @@ msgstr "We encountered an unknown error while attempting update your profile. Pl msgid "We have sent a confirmation email for verification." msgstr "We have sent a confirmation email for verification." -#: apps/remix/app/components/forms/signup.tsx -msgid "We need a username to create your profile" -msgstr "We need a username to create your profile" - #: apps/remix/app/components/forms/signup.tsx msgid "We need your signature to sign documents" msgstr "We need your signature to sign documents" @@ -6962,10 +7179,6 @@ msgstr "We were unable to copy the token to your clipboard. Please try again." msgid "We were unable to copy your recovery code to your clipboard. Please try again." msgstr "We were unable to copy your recovery code to your clipboard. Please try again." -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "We were unable to create a checkout session. Please try again, or contact support" -msgstr "We were unable to create a checkout session. Please try again, or contact support" - #: apps/remix/app/components/forms/signup.tsx msgid "We were unable to create your account. Please review the information you provided and try again." msgstr "We were unable to create your account. Please review the information you provided and try again." @@ -6995,7 +7208,7 @@ msgstr "We were unable to setup two-factor authentication for your account. Plea msgid "We were unable to submit this document at this time. Please try again later." msgstr "We were unable to submit this document at this time. Please try again later." -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx msgid "We were unable to update your branding preferences at this time, please try again later" msgstr "We were unable to update your branding preferences at this time, please try again later" @@ -7068,10 +7281,6 @@ msgstr "Webhook URL" msgid "Webhooks" msgstr "Webhooks" -#: apps/remix/app/components/general/billing-plans.tsx -msgid "Weekly" -msgstr "Weekly" - #: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx msgid "Welcome" msgstr "Welcome" @@ -7117,6 +7326,10 @@ msgstr "When you use our platform to affix your electronic signature to document msgid "While waiting for them to do so you can create your own Documenso account and get started with document signing right away." msgstr "While waiting for them to do so you can create your own Documenso account and get started with document signing right away." +#: packages/lib/types/subscription.ts +msgid "Whitelabeling, unlimited members and more" +msgstr "Whitelabeling, unlimited members and more" + #: apps/remix/app/components/dialogs/document-resend-dialog.tsx msgid "Who do you want to remind?" msgstr "Who do you want to remind?" @@ -7134,6 +7347,7 @@ msgid "Write about yourself" msgstr "Write about yourself" #: apps/remix/app/components/general/billing-plans.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Yearly" msgstr "Yearly" @@ -7203,6 +7417,10 @@ msgstr "You are about to revoke access for team <0>{0} ({1}) to use your ema msgid "You are about to send this document to the recipients. Are you sure you want to continue?" msgstr "You are about to send this document to the recipients. Are you sure you want to continue?" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "You are currently on the <0>Free Plan." +msgstr "You are currently on the <0>Free Plan." + #: apps/remix/app/components/dialogs/team-member-update-dialog.tsx msgid "You are currently updating <0>{memberName}." msgstr "You are currently updating <0>{memberName}." @@ -7309,14 +7527,14 @@ msgstr "You cannot upload documents at this time." msgid "You cannot upload encrypted PDFs" msgstr "You cannot upload encrypted PDFs" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "You currently have an inactive <0>{currentProductName} subscription" +msgstr "You currently have an inactive <0>{currentProductName} subscription" + #: apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx msgid "You currently have no access to any teams within this organisation. Please contact your organisation to request access." msgstr "You currently have no access to any teams within this organisation. Please contact your organisation to request access." -#: apps/remix/app/components/general/billing-portal-button.tsx -msgid "You do not currently have a customer record, this should not happen. Please contact support for assistance." -msgstr "You do not currently have a customer record, this should not happen. Please contact support for assistance." - #: apps/remix/app/components/forms/token.tsx msgid "You do not have permission to create a token for this team" msgstr "You do not have permission to create a token for this team" @@ -7349,10 +7567,6 @@ msgstr "You have been invited to join {0} on Documenso" msgid "You have been invited to join the following organisation" msgstr "You have been invited to join the following organisation" -#: packages/email/templates/organisation-invite.tsx -msgid "You have been invited to join the following team" -msgstr "You have been invited to join the following team" - #: packages/lib/server-only/recipient/set-document-recipients.ts #: packages/lib/server-only/recipient/delete-document-recipient.ts msgid "You have been removed from a document" @@ -7386,6 +7600,10 @@ msgstr "You have not yet created or received any documents. To create a document msgid "You have reached the maximum limit of {0} direct templates. <0>Upgrade your account to continue!" msgstr "You have reached the maximum limit of {0} direct templates. <0>Upgrade your account to continue!" +#: apps/remix/app/components/dialogs/team-create-dialog.tsx +msgid "You have reached the maximum number of teams for your plan. Please contact sales at <0>support@documenso.com if you would like to adjust your plan." +msgstr "You have reached the maximum number of teams for your plan. Please contact sales at <0>support@documenso.com if you would like to adjust your plan." + #: apps/remix/app/components/general/document/document-upload.tsx msgid "You have reached your document limit for this month. Please upgrade your plan." msgstr "You have reached your document limit for this month. Please upgrade your plan." @@ -7485,10 +7703,6 @@ msgstr "You need to be logged in to view this page." msgid "You need to setup 2FA to mark this document as viewed." msgstr "You need to setup 2FA to mark this document as viewed." -#: apps/remix/app/components/forms/signup.tsx -msgid "You will get notified & be able to set up your documenso public profile when we launch the feature." -msgstr "You will get notified & be able to set up your documenso public profile when we launch the feature." - #: apps/remix/app/components/forms/2fa/enable-authenticator-app-dialog.tsx msgid "You will now be required to enter a code from your authenticator app when signing in." msgstr "You will now be required to enter a code from your authenticator app when signing in." @@ -7509,11 +7723,11 @@ msgstr "Your avatar has been updated successfully." msgid "Your banner has been updated successfully." msgstr "Your banner has been updated successfully." -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Your brand website URL" msgstr "Your brand website URL" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx msgid "Your branding preferences have been updated" msgstr "Your branding preferences have been updated" @@ -7525,6 +7739,18 @@ msgstr "Your bulk send has been initiated. You will receive an email notificatio msgid "Your bulk send operation for template \"{templateName}\" has completed." msgstr "Your bulk send operation for template \"{templateName}\" has completed." +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "Your current {currentProductName} plan is past due. Please update your payment information." +msgstr "Your current {currentProductName} plan is past due. Please update your payment information." + +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "Your current plan is inactive." +msgstr "Your current plan is inactive." + +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "Your current plan is past due." +msgstr "Your current plan is past due." + #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx msgid "Your direct signing templates" msgstr "Your direct signing templates" @@ -7614,7 +7840,7 @@ msgstr "Your password has been updated successfully." msgid "Your password has been updated." msgstr "Your password has been updated." -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Your payment for teams is overdue. Please settle the payment to avoid any service disruptions." msgstr "Your payment for teams is overdue. Please settle the payment to avoid any service disruptions." diff --git a/packages/lib/translations/es/web.po b/packages/lib/translations/es/web.po index e9a5708f0..b7bb659de 100644 --- a/packages/lib/translations/es/web.po +++ b/packages/lib/translations/es/web.po @@ -73,11 +73,6 @@ msgstr "{0, plural, one {# carácter sobre el límite} other {# caracteres sobre msgid "{0, plural, one {# recipient} other {# recipients}}" msgstr "{0, plural, one {# destinatario} other {# destinatarios}}" -#. placeholder {0}: row.original.quantity -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx -msgid "{0, plural, one {# Seat} other {# Seats}}" -msgstr "{0, plural, one {# Asiento} other {# Asientos}}" - #. placeholder {0}: org.teams.length #: apps/remix/app/routes/_authenticated+/dashboard.tsx msgid "{0, plural, one {# team} other {# teams}}" @@ -132,16 +127,6 @@ msgstr "{0} te ha invitado a {recipientActionVerb} el documento \"{1}\"." msgid "{0} invited you to {recipientActionVerb} a document" msgstr "{0} te invitó a {recipientActionVerb} un documento" -#. placeholder {0}: memberName || memberEmail -#: packages/email/templates/team-join.tsx -msgid "{0} joined the team {teamName} on Documenso" -msgstr "{0} se unió al equipo {teamName} en Documenso" - -#. placeholder {0}: memberName || memberEmail -#: packages/email/templates/team-leave.tsx -msgid "{0} left the team {teamName} on Documenso" -msgstr "{0} dejó el equipo {teamName} en Documenso" - #. placeholder {0}: remaining.documents #. placeholder {1}: quota.documents #: apps/remix/app/components/general/document/document-upload.tsx @@ -223,14 +208,6 @@ msgstr "{inviterName} en nombre de \"{teamName}\" te ha invitado a {0}<0/>\"{doc msgid "{inviterName} on behalf of \"{teamName}\" has invited you to {action} {documentName}" msgstr "{inviterName} en nombre de \"{teamName}\" te ha invitado a {action} {documentName}" -#: packages/email/templates/team-join.tsx -msgid "{memberEmail} joined the following team" -msgstr "{memberEmail} se unió al siguiente equipo" - -#: packages/email/templates/team-leave.tsx -msgid "{memberEmail} left the following team" -msgstr "{memberEmail} dejó el siguiente equipo" - #: packages/lib/utils/document-audit-logs.ts msgid "{prefix} added a field" msgstr "{prefix} agregó un campo" @@ -484,6 +461,14 @@ msgstr "<0>Está a punto de completar la firma de \"<1>{documentTitle}\".You are about to complete viewing \"<1>{documentTitle}\".<2/> Are you sure?" msgstr "<0>Está a punto de completar la visualización de \"<1>{documentTitle}\".<2/> ¿Está seguro?" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "0 Free organisations left" +msgstr "" + +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "1 Free organisations left" +msgstr "" + #: apps/remix/app/components/forms/token.tsx msgid "1 month" msgstr "1 mes" @@ -510,6 +495,7 @@ msgid "404 Organisation group not found" msgstr "" #: apps/remix/app/routes/_authenticated+/_layout.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "404 Organisation not found" msgstr "" @@ -522,6 +508,14 @@ msgstr "404 Perfil no encontrado" msgid "404 Team not found" msgstr "404 Equipo no encontrado" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "5 documents a month" +msgstr "" + +#: packages/lib/types/subscription.ts +msgid "5 Documents a month" +msgstr "" + #: apps/remix/app/components/general/generic-error-layout.tsx msgid "500 Internal Server Error" msgstr "" @@ -566,9 +560,29 @@ msgstr "Se actualizó un campo" msgid "A means to print or download documents for your records" msgstr "Un medio para imprimir o descargar documentos para sus registros" -#: packages/lib/jobs/definitions/emails/send-team-member-joined-email.handler.ts -msgid "A new member has joined your team" -msgstr "Un nuevo miembro se ha unido a tu equipo" +#: packages/email/templates/organisation-join.tsx +msgid "A member has joined your organisation on Documenso" +msgstr "" + +#: packages/lib/jobs/definitions/emails/send-organisation-member-left-email.handler.ts +msgid "A member has left your organisation" +msgstr "" + +#: packages/email/templates/organisation-leave.tsx +msgid "A member has left your organisation {organisationName}" +msgstr "" + +#: packages/email/templates/organisation-leave.tsx +msgid "A member has left your organisation on Documenso" +msgstr "" + +#: packages/lib/jobs/definitions/emails/send-organisation-member-joined-email.handler.ts +msgid "A new member has joined your organisation" +msgstr "" + +#: packages/email/templates/organisation-join.tsx +msgid "A new member has joined your organisation {organisationName}" +msgstr "" #: apps/remix/app/components/forms/token.tsx msgid "A new token was created successfully." @@ -608,19 +622,6 @@ msgstr "Un secreto que se enviará a tu URL para que puedas verificar que la sol msgid "A stable internet connection" msgstr "Una conexión a Internet estable" -#: packages/email/templates/team-join.tsx -msgid "A team member has joined a team on Documenso" -msgstr "Un miembro del equipo se ha unido a un equipo en Documenso" - -#. placeholder {0}: team.name -#: packages/lib/jobs/definitions/emails/send-team-member-left-email.handler.ts -msgid "A team member has left {0}" -msgstr "Un miembro del equipo ha dejado {0}" - -#: packages/email/templates/team-leave.tsx -msgid "A team member has left a team on Documenso" -msgstr "Un miembro del equipo ha dejado un equipo en Documenso" - #: packages/email/templates/team-delete.tsx #: packages/email/templates/team-delete.tsx msgid "A team you were a part of has been deleted" @@ -630,8 +631,11 @@ msgstr "Un equipo del que formabas parte ha sido eliminado" msgid "A unique URL to access your profile" msgstr "Una URL única para acceder a tu perfil" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "A unique URL to identify the organisation" +msgstr "" + #: apps/remix/app/components/forms/organisation-update-form.tsx -#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "A unique URL to identify your organisation" msgstr "" @@ -651,8 +655,8 @@ msgid "Accept" msgstr "Aceptar" #: packages/email/templates/organisation-invite.tsx -msgid "Accept invitation to join a team on Documenso" -msgstr "Aceptar invitación para unirse a un equipo en Documenso" +msgid "Accept invitation to join an organisation on Documenso" +msgstr "" #: packages/email/templates/confirm-team-email.tsx msgid "Accept team email request for {teamName} on Documenso" @@ -720,11 +724,12 @@ msgstr "Acción" #: apps/remix/app/components/tables/organisation-groups-table.tsx #: apps/remix/app/components/tables/inbox-table.tsx #: apps/remix/app/components/tables/documents-table.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx #: apps/remix/app/components/general/template/template-page-view-documents-table.tsx msgid "Actions" msgstr "Acciones" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.teams.tsx #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.members.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx msgid "Active" @@ -865,11 +870,7 @@ msgstr "Agrega a las personas que firmarán el documento." msgid "Add the recipients to create the document with" msgstr "Agrega los destinatarios con los que crear el documento" -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "Adding and removing seats will adjust your invoice accordingly." -msgstr "Agregar y eliminar asientos ajustará tu factura en consecuencia." - -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Additional brand information to display at the bottom of emails" msgstr "Información adicional de la marca para mostrar al final de los correos electrónicos" @@ -886,6 +887,10 @@ msgstr "Acciones Administrativas" msgid "Admin panel" msgstr "Panel administrativo" +#: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx +msgid "Admin Panel" +msgstr "" + #: packages/ui/primitives/template-flow/add-template-settings.tsx #: packages/ui/primitives/document-flow/add-settings.tsx msgid "Advanced Options" @@ -962,6 +967,10 @@ msgstr "" msgid "Allowed Signature Types" msgstr "" +#: apps/remix/app/components/tables/admin-claims-table.tsx +msgid "Allowed teams" +msgstr "" + #: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx msgid "Allows authenticating using biometrics, password managers, hardware keys, etc." msgstr "Permite autenticarse usando biometría, administradores de contraseñas, claves de hardware, etc." @@ -970,7 +979,7 @@ msgstr "Permite autenticarse usando biometría, administradores de contraseñas, msgid "Already have an account? <0>Sign in instead" msgstr "¿Ya tienes una cuenta? <0>Iniciar sesión en su lugar" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx msgid "Amount" msgstr "Cantidad" @@ -991,6 +1000,8 @@ msgid "An email containing an invitation will be sent to each member." msgstr "Un correo electrónico que contiene una invitación se enviará a cada miembro." #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/general/claim-account.tsx #: apps/remix/app/components/forms/token.tsx #: apps/remix/app/components/forms/signup.tsx @@ -1230,6 +1241,10 @@ msgstr "Aprobando" msgid "Are you sure you want to complete the document? This action cannot be undone. Please ensure that you have completed prefilling all relevant fields before proceeding." msgstr "" +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "Are you sure you want to delete the following claim?" +msgstr "" + #: apps/remix/app/components/dialogs/token-delete-dialog.tsx msgid "Are you sure you want to delete this token?" msgstr "¿Estás seguro de que deseas eliminar este token?" @@ -1340,9 +1355,9 @@ msgid "Awaiting email confirmation" msgstr "Esperando confirmación de correo electrónico" #: apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx -#: apps/remix/app/components/forms/signup.tsx #: apps/remix/app/components/dialogs/team-member-create-dialog.tsx #: apps/remix/app/components/dialogs/team-group-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Back" msgstr "Atrás" @@ -1367,29 +1382,17 @@ msgstr "Códigos de respaldo" msgid "Banner Updated" msgstr "Banner actualizado" -#: apps/remix/app/components/forms/signup.tsx -msgid "Basic details" -msgstr "Detalles básicos" - #: packages/email/template-components/template-confirmation-email.tsx msgid "Before you get started, please confirm your email address by clicking the button below:" msgstr "Antes de comenzar, por favor confirma tu dirección de correo electrónico haciendo clic en el botón de abajo:" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings._layout.tsx -#: apps/remix/app/routes/_authenticated+/settings+/billing.tsx -#: apps/remix/app/components/general/settings-nav-mobile.tsx -#: apps/remix/app/components/general/settings-nav-desktop.tsx #: apps/remix/app/components/general/teams/team-settings-nav-mobile.tsx #: apps/remix/app/components/general/teams/team-settings-nav-desktop.tsx msgid "Billing" msgstr "Facturación" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx -#: apps/remix/app/routes/_authenticated+/settings+/billing.tsx -msgid "Billing has been moved to organisations" -msgstr "" - #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx msgid "Black" msgstr "Negro" @@ -1398,12 +1401,13 @@ msgstr "Negro" msgid "Blue" msgstr "Azul" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx msgid "Branding Preferences" msgstr "Preferencias de marca" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx msgid "Branding preferences updated" msgstr "Preferencias de marca actualizadas" @@ -1512,7 +1516,7 @@ msgstr "" #: apps/remix/app/components/dialogs/team-email-add-dialog.tsx #: apps/remix/app/components/dialogs/team-delete-dialog.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx +#: apps/remix/app/components/dialogs/team-create-dialog.tsx #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx #: apps/remix/app/components/dialogs/organisation-member-update-dialog.tsx @@ -1523,9 +1527,13 @@ msgstr "" #: apps/remix/app/components/dialogs/organisation-group-create-dialog.tsx #: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx #: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx #: apps/remix/app/components/dialogs/document-resend-dialog.tsx #: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx #: apps/remix/app/components/dialogs/document-delete-dialog.tsx +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx #: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx #: packages/ui/primitives/signature-pad/signature-pad-dialog.tsx #: packages/ui/primitives/document-flow/send-document-action-dialog.tsx @@ -1574,7 +1582,7 @@ msgstr "Casilla de verificación" msgid "Checkbox values" msgstr "Valores de Checkbox" -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Checkout" msgstr "Checkout" @@ -1598,13 +1606,9 @@ msgstr "Elija..." msgid "Claim account" msgstr "Reclamar cuenta" -#: apps/remix/app/components/forms/signup.tsx -msgid "Claim username" -msgstr "Reclamar nombre de usuario" - -#: apps/remix/app/components/forms/signup.tsx -msgid "Claim your username now" -msgstr "Reclame su nombre de usuario ahora" +#: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx +msgid "Claims" +msgstr "" #: apps/remix/app/components/dialogs/template-use-dialog.tsx msgid "Clear file" @@ -1660,6 +1664,10 @@ msgstr "Haga clic para insertar campo" msgid "Close" msgstr "Cerrar" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Compare all plans and features in detail" +msgstr "" + #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx #: apps/remix/app/components/forms/signup.tsx @@ -1782,6 +1790,10 @@ msgstr "Consentimiento para Transacciones Electrónicas" msgid "Contact Information" msgstr "Información de Contacto" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Contact sales here" +msgstr "" + #: apps/remix/app/routes/_authenticated+/admin+/site-settings.tsx msgid "Content" msgstr "Contenido" @@ -1793,6 +1805,7 @@ msgstr "Contenido" #: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx #: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx #: packages/ui/primitives/document-flow/document-flow-root.tsx msgid "Continue" @@ -1892,6 +1905,7 @@ msgstr "Copiar token" #: apps/remix/app/routes/_profile+/_layout.tsx #: apps/remix/app/components/dialogs/webhook-create-dialog.tsx #: apps/remix/app/components/dialogs/organisation-group-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Create" msgstr "Crear" @@ -1928,6 +1942,14 @@ msgstr "Crear como borrador" msgid "Create as pending" msgstr "Crear como pendiente" +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Create claim" +msgstr "" + +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Create Claim" +msgstr "" + #: apps/remix/app/components/dialogs/template-direct-link-dialog-wrapper.tsx msgid "Create Direct Link" msgstr "Crear enlace directo" @@ -1962,18 +1984,26 @@ msgstr "Crear uno automáticamente" msgid "Create organisation" msgstr "" -#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx -msgid "Create Organisation" -msgstr "" - #: apps/remix/app/components/general/menu-switcher.tsx -msgid "Create Organization" +msgid "Create Organisation" msgstr "" #: apps/remix/app/components/dialogs/template-use-dialog.tsx msgid "Create signing links" msgstr "Crear enlaces de firma" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Create Stripe customer" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Create subscription" +msgstr "" + +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Create Subscription Claim" +msgstr "" + #: apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx @@ -2025,7 +2055,7 @@ msgid "Created" msgstr "Creado" #: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Created At" msgstr "Creado En" @@ -2034,7 +2064,6 @@ msgid "Created by" msgstr "Creado por" #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx -#: apps/remix/app/components/tables/organisation-pending-teams-table.tsx msgid "Created on" msgstr "Creado el" @@ -2065,12 +2094,16 @@ msgstr "Destinatarios actuales:" msgid "Currently all organisation members can access this team" msgstr "" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups._index.tsx -msgid "Custom Organisation Groups" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx +msgid "Currently branding can only be configured for Teams and above plans." msgstr "" -#: apps/remix/app/components/general/billing-plans.tsx -msgid "Daily" +#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx +msgid "Currently branding can only be configured on the organisation level." +msgstr "" + +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups._index.tsx +msgid "Custom Organisation Groups" msgstr "" #: apps/remix/app/components/general/app-command-menu.tsx @@ -2130,16 +2163,20 @@ msgstr "eliminar" #: apps/remix/app/components/tables/organisation-teams-table.tsx #: apps/remix/app/components/tables/organisation-groups-table.tsx #: apps/remix/app/components/tables/documents-table-action-dropdown.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx #: apps/remix/app/components/general/document/document-page-view-dropdown.tsx #: apps/remix/app/components/dialogs/webhook-delete-dialog.tsx #: apps/remix/app/components/dialogs/token-delete-dialog.tsx #: apps/remix/app/components/dialogs/template-delete-dialog.tsx #: apps/remix/app/components/dialogs/team-group-delete-dialog.tsx #: apps/remix/app/components/dialogs/team-delete-dialog.tsx +#: apps/remix/app/components/dialogs/team-delete-dialog.tsx #: apps/remix/app/components/dialogs/organisation-member-delete-dialog.tsx #: apps/remix/app/components/dialogs/organisation-group-delete-dialog.tsx #: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx #: apps/remix/app/components/dialogs/document-delete-dialog.tsx +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx msgid "Delete" msgstr "Eliminar" @@ -2179,7 +2216,6 @@ msgid "Delete Document" msgstr "Eliminar Documento" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.general.tsx -#: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx msgid "Delete organisation" msgstr "" @@ -2195,8 +2231,11 @@ msgstr "" msgid "Delete passkey" msgstr "Eliminar clave de paso" +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "Delete Subscription Claim" +msgstr "" + #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx -#: apps/remix/app/components/dialogs/team-delete-dialog.tsx msgid "Delete team" msgstr "Eliminar equipo" @@ -2620,7 +2659,7 @@ msgstr "Documentos vistos" msgid "Don't have an account? <0>Sign up" msgstr "¿No tienes una cuenta? <0>Regístrate" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx #: apps/remix/app/components/tables/documents-table-action-dropdown.tsx #: apps/remix/app/components/tables/documents-table-action-button.tsx #: apps/remix/app/components/general/document/document-page-view-dropdown.tsx @@ -2675,7 +2714,7 @@ msgstr "Menú desplegable" msgid "Dropdown options" msgstr "Opciones de menú desplegable" -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Due to an unpaid invoice, your team has been restricted. Please settle the payment to restore full access to your team." msgstr "Debido a una factura impaga, tu equipo ha sido restringido. Realiza el pago para restaurar el acceso completo a tu equipo." @@ -2718,6 +2757,7 @@ msgstr "Divulgación de Firma Electrónica" #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-dashboard-users-table.tsx #: apps/remix/app/components/general/document-signing/document-signing-email-field.tsx @@ -2790,6 +2830,10 @@ msgstr "La verificación de correo electrónico ha sido eliminada" msgid "Email verification has been resent" msgstr "La verificación de correo electrónico ha sido reenviada" +#: packages/lib/types/subscription.ts +msgid "Embedding, 5 members included and more" +msgstr "" + #: packages/ui/primitives/document-flow/add-fields.tsx msgid "Empty field" msgstr "Campo vacío" @@ -2820,7 +2864,7 @@ msgstr "Habilitar Cuenta" msgid "Enable Authenticator App" msgstr "Habilitar aplicación autenticadora" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Enable custom branding for all documents in this team." msgstr "Habilitar branding personalizado para todos los documentos en este equipo." @@ -2849,11 +2893,11 @@ msgstr "Habilitar la cuenta permite al usuario usar la cuenta de nuevo, junto co msgid "Enclosed Document" msgstr "Documento Adjunto" -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx -msgid "Ends On" -msgstr "Termina en" +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Enter claim name" +msgstr "" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Enter your brand details" msgstr "Ingresa los detalles de tu marca" @@ -2873,7 +2917,12 @@ msgstr "Ingresa tu nombre" msgid "Enter your text here" msgstr "Ingresa tu texto aquí" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Enterprise" +msgstr "" + #: apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx #: apps/remix/app/components/general/verify-email-banner.tsx #: apps/remix/app/components/general/template/template-edit-form.tsx @@ -2948,6 +2997,14 @@ msgstr "Expira el {0}" msgid "External ID" msgstr "ID externo" +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Failed to create subscription claim." +msgstr "" + +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "Failed to delete subscription claim." +msgstr "" + #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx msgid "Failed to reseal document" msgstr "Falló al volver a sellar el documento" @@ -2960,6 +3017,10 @@ msgstr "Fallo al guardar configuraciones." msgid "Failed to update recipient" msgstr "Falló al actualizar el destinatario" +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Failed to update subscription claim." +msgstr "" + #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx msgid "Failed to update webhook" msgstr "Falló al actualizar el webhook" @@ -2968,6 +3029,12 @@ msgstr "Falló al actualizar el webhook" msgid "Failed: {failedCount}" msgstr "Fallidos: {failedCount}" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Feature Flags" +msgstr "" + #: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx msgid "Field character limit" msgstr "Límite de caracteres del campo" @@ -3019,6 +3086,10 @@ msgstr "El archivo no puede ser mayor a {APP_DOCUMENT_UPLOAD_SIZE_LIMIT}MB" msgid "File size exceeds the limit of {APP_DOCUMENT_UPLOAD_SIZE_LIMIT} MB" msgstr "El tamaño del archivo excede el límite de {APP_DOCUMENT_UPLOAD_SIZE_LIMIT} MB" +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Fill in the details to create a new subscription claim." +msgstr "" + #: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx #: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx #: packages/ui/primitives/document-flow/field-items-advanced-settings/name-field.tsx @@ -3036,6 +3107,10 @@ msgstr "Si tiene alguna pregunta sobre esta divulgación, firmas electrónicas o msgid "For each recipient, provide their email (required) and name (optional) in separate columns. Download the template CSV below for the correct format." msgstr "Para cada destinatario, proporciona su correo electrónico (obligatorio) y nombre (opcional) en columnas separadas. Descarga el modelo CSV a continuación para el formato correcto." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "For example, if the claim has a new flag \"FLAG_1\" set to true, then this organisation will get that flag added." +msgstr "" + #: packages/lib/server-only/auth/send-forgot-password.ts msgid "Forgot Password?" msgstr "¿Olvidaste tu contraseña?" @@ -3046,6 +3121,10 @@ msgstr "¿Olvidaste tu contraseña?" msgid "Forgot your password?" msgstr "¿Olvidaste tu contraseña?" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Free" +msgstr "" + #: packages/ui/primitives/document-flow/types.ts msgid "Free Signature" msgstr "Firma gratuita" @@ -3079,6 +3158,7 @@ msgid "Global recipient action authentication" msgstr "Autenticación de acción de destinatario global" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "Go back" msgstr "" @@ -3193,7 +3273,6 @@ msgid "Here you can manage your password and security settings." msgstr "Aquí puedes gestionar tu contraseña y la configuración de seguridad." #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx -#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx msgid "Here you can set preferences and defaults for branding." msgstr "Aquí puedes establecer preferencias y valores predeterminados para la marca." @@ -3260,6 +3339,14 @@ msgstr "Soy el propietario de este documento" msgid "I'm sure! Delete it" msgstr "¡Estoy seguro! Elimínalo" +#: apps/remix/app/components/tables/admin-claims-table.tsx +msgid "ID" +msgstr "" + +#: apps/remix/app/components/tables/admin-claims-table.tsx +msgid "ID copied to clipboard" +msgstr "" + #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx msgid "If you do not want to use the authenticator prompted, you can close it, which will then display the next available authenticator." msgstr "Si no deseas usar el autenticador solicitado, puedes cerrarlo, lo que mostrará el próximo autenticador disponible." @@ -3298,6 +3385,7 @@ msgstr "Heredar método de autenticación" #: apps/remix/app/components/forms/document-preferences-form.tsx #: apps/remix/app/components/forms/document-preferences-form.tsx #: apps/remix/app/components/forms/document-preferences-form.tsx +#: apps/remix/app/components/forms/document-preferences-form.tsx msgid "Inherit from organisation" msgstr "" @@ -3305,6 +3393,11 @@ msgstr "" msgid "Inherit organisation members" msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Inherited subscription claim" +msgstr "" + #: apps/remix/app/components/general/document-signing/document-signing-initials-field.tsx #: packages/ui/primitives/document-flow/types.ts msgid "Initials" @@ -3385,7 +3478,7 @@ msgstr "" msgid "Invited At" msgstr "Invitado el" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx msgid "Invoice" msgstr "Factura" @@ -3479,7 +3572,7 @@ msgstr "Último uso" msgid "Leaderboard" msgstr "Tabla de clasificación" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/dialogs/organisation-leave-dialog.tsx msgid "Leave" msgstr "Salir" @@ -3541,8 +3634,10 @@ msgstr "Cargando..." msgid "Login" msgstr "Iniciar sesión" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/tables/organisation-teams-table.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Manage" msgstr "Gestionar" @@ -3555,10 +3650,18 @@ msgstr "Gestionar el perfil de {0}" msgid "Manage all organisations you are currently associated with." msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/claims.tsx +msgid "Manage all subscription claims" +msgstr "" + #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/templates.$id._index.tsx msgid "Manage and view template" msgstr "Gestionar y ver plantilla" +#: apps/remix/app/components/general/organisations/organisation-billing-portal-button.tsx +msgid "Manage billing" +msgstr "" + #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx msgid "Manage details for this public template" msgstr "Gestionar detalles de esta plantilla pública" @@ -3571,6 +3674,14 @@ msgstr "Gestionar enlace directo" msgid "Manage documents" msgstr "Gestionar documentos" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Manage organisation" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx +msgid "Manage organisations" +msgstr "" + #: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx msgid "Manage passkeys" msgstr "Gestionar claves de acceso" @@ -3579,17 +3690,20 @@ msgstr "Gestionar claves de acceso" msgid "Manage permissions and access controls" msgstr "" -#: apps/remix/app/components/general/teams/team-billing-portal-button.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "Manage subscription" msgstr "Gestionar suscripción" -#: apps/remix/app/components/general/billing-portal-button.tsx -msgid "Manage Subscription" +#. placeholder {0}: organisation.name +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Manage the {0} organisation" msgstr "" -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx -msgid "Manage subscriptions" -msgstr "Gestionar suscripciones" +#. placeholder {1}: organisation.name +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Manage the {1} organisation subscription" +msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups._index.tsx msgid "Manage the custom groups of members for your organisation." @@ -3661,13 +3775,19 @@ msgstr "Máx" msgid "Maximum file size: 4MB. Maximum 100 rows per upload. Blank values will use template defaults." msgstr "Tamaño máximo de archivo: 4MB. Máximo 100 filas por carga. Los valores en blanco usarán los valores predeterminados de la plantilla." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/dialogs/team-member-create-dialog.tsx #: packages/lib/constants/teams.ts #: packages/lib/constants/organisations.ts msgid "Member" msgstr "Miembro" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Member Count" +msgstr "" + +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/tables/organisation-members-table.tsx msgid "Member Since" msgstr "Miembro desde" @@ -3698,7 +3818,12 @@ msgstr "Mín" msgid "Modify recipients" msgstr "Modificar destinatarios" +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Modify the details of the subscription claim." +msgstr "" + #: apps/remix/app/components/general/billing-plans.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Monthly" msgstr "Mensual" @@ -3716,9 +3841,11 @@ msgstr "Usuarios activos mensuales: Usuarios que completaron al menos uno de sus #: apps/remix/app/components/tables/admin-leaderboard-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-dashboard-users-table.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx #: apps/remix/app/components/general/claim-account.tsx #: apps/remix/app/components/general/document-signing/document-signing-name-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx @@ -3771,7 +3898,6 @@ msgstr "" msgid "New Template" msgstr "Nueva plantilla" -#: apps/remix/app/components/forms/signup.tsx #: apps/remix/app/components/embed/embed-document-signing-page.tsx #: apps/remix/app/components/embed/embed-direct-template-client-page.tsx #: apps/remix/app/components/dialogs/team-member-create-dialog.tsx @@ -3846,6 +3972,10 @@ msgstr "No se encontraron resultados." msgid "No signature field found" msgstr "No se encontró campo de firma" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "No Stripe customer attached" +msgstr "" + #: apps/remix/app/components/tables/team-groups-table.tsx msgid "No team groups found" msgstr "" @@ -3873,6 +4003,7 @@ msgstr "No se encontró valor." msgid "No worries, it happens! Enter your email and we'll email you a special link to reset your password." msgstr "¡No te preocupes, sucede! Ingresa tu correo electrónico y te enviaremos un enlace especial para restablecer tu contraseña." +#: apps/remix/app/components/tables/admin-claims-table.tsx #: packages/lib/constants/document.ts msgid "None" msgstr "Ninguno" @@ -3897,6 +4028,16 @@ msgstr "Número" msgid "Number format" msgstr "Formato de número" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Number of members allowed. 0 = Unlimited" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Number of teams allowed. 0 = Unlimited" +msgstr "" + #. placeholder {0}: document.team?.name #: apps/remix/app/components/general/document-signing/document-signing-page-view.tsx msgid "on behalf of \"{0}\" has invited you to approve this document" @@ -3957,10 +4098,6 @@ msgstr "Solo los administradores pueden acceder y ver el documento" msgid "Only managers and above can access and view the document" msgstr "Solo los gerentes y superiores pueden acceder y ver el documento" -#: apps/remix/app/components/forms/signup.tsx -msgid "Only subscribers can have a username shorter than 6 characters" -msgstr "Solo los suscriptores pueden tener un nombre de usuario de menos de 6 caracteres" - #: apps/remix/app/routes/_profile+/_layout.tsx #: apps/remix/app/components/general/generic-error-layout.tsx #: apps/remix/app/components/general/generic-error-layout.tsx @@ -3980,7 +4117,8 @@ msgstr "O" msgid "Or continue with" msgstr "O continúa con" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Organisation" msgstr "" @@ -3996,6 +4134,11 @@ msgstr "" msgid "Organisation Group Settings" msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Organisation has been updated successfully" +msgstr "" + #: apps/remix/app/routes/_unauthenticated+/organisation.invite.$token.tsx msgid "Organisation invitation" msgstr "" @@ -4015,15 +4158,18 @@ msgid "Organisation Member" msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.members.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "Organisation Members" msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/forms/organisation-update-form.tsx #: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Organisation Name" msgstr "" #: apps/remix/app/routes/_authenticated+/_layout.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "Organisation not found" msgstr "" @@ -4036,6 +4182,7 @@ msgid "Organisation role" msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/components/dialogs/organisation-member-invite-dialog.tsx msgid "Organisation Role" msgstr "" @@ -4047,13 +4194,18 @@ msgstr "" msgid "Organisation Settings" msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Organisation Teams" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/forms/organisation-update-form.tsx -#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Organisation URL" msgstr "" #: apps/remix/app/routes/_authenticated+/dashboard.tsx #: apps/remix/app/routes/_authenticated+/settings+/organisations.tsx +#: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx #: apps/remix/app/components/general/settings-nav-mobile.tsx #: apps/remix/app/components/general/settings-nav-desktop.tsx #: apps/remix/app/components/general/menu-switcher.tsx @@ -4076,8 +4228,9 @@ msgstr "De lo contrario, el documento se creará como un borrador." #: apps/remix/app/routes/_authenticated+/dashboard.tsx #: apps/remix/app/routes/_authenticated+/dashboard.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/tables/organisation-members-table.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Owner" msgstr "Propietario" @@ -4092,10 +4245,6 @@ msgstr "Página {0} de {1}" msgid "Page {0} of {numPages}" msgstr "Página {0} de {numPages}" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx -msgid "Paid" -msgstr "Pagado" - #: apps/remix/app/components/forms/signin.tsx msgid "Passkey" msgstr "Clave de acceso" @@ -4166,20 +4315,11 @@ msgstr "Contraseña actualizada" msgid "Password updated!" msgstr "¡Contraseña actualizada!" -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx -msgid "Pay" -msgstr "Pagar" - -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "Payment is required to finalise the creation of your team." -msgstr "Se requiere pago para finalizar la creación de tu equipo." - -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Payment overdue" msgstr "Pago atrasado" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.teams.tsx #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.members.tsx #: apps/remix/app/components/general/template/template-page-view-documents-table.tsx #: apps/remix/app/components/general/document/document-status.tsx @@ -4205,9 +4345,15 @@ msgstr "Documentos Pendientes" msgid "Pending invitations" msgstr "Invitaciones pendientes" -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx -msgid "Pending team deleted." -msgstr "Equipo pendiente eliminado." +#: apps/remix/app/components/general/billing-plans.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "per month" +msgstr "" + +#: apps/remix/app/components/general/billing-plans.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "per year" +msgstr "" #: apps/remix/app/components/general/menu-switcher.tsx msgid "Personal Account" @@ -4369,6 +4515,10 @@ msgstr "Por favor, escriba {0} para confirmar" msgid "Please type <0>{0} to confirm." msgstr "Por favor, escribe <0>{0} para confirmar." +#: apps/remix/app/components/forms/branding-preferences-form.tsx +msgid "Please upload a logo" +msgstr "" + #: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx msgid "Pre-formatted CSV template with example data." msgstr "Plantilla CSV preformateada con datos de ejemplo." @@ -4435,10 +4585,6 @@ msgstr "Perfil Público" msgid "Public profile URL" msgstr "URL del perfil público" -#: apps/remix/app/components/forms/signup.tsx -msgid "Public profile username" -msgstr "Nombre de usuario del perfil público" - #: apps/remix/app/components/tables/templates-table.tsx msgid "Public templates are connected to your public profile. Any modifications to public templates will also appear in your public profile." msgstr "Las plantillas públicas están conectadas a tu perfil público. Cualquier modificación a las plantillas públicas también aparecerá en tu perfil público." @@ -4621,7 +4767,6 @@ msgstr "Recordatorio: Por favor {recipientActionVerb} este documento" msgid "Reminder: Please {recipientActionVerb} your document" msgstr "Recordatorio: Por favor {recipientActionVerb} tu documento" -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx #: apps/remix/app/components/tables/team-members-table.tsx #: apps/remix/app/components/tables/team-groups-table.tsx #: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx @@ -4630,7 +4775,7 @@ msgstr "Recordatorio: Por favor {recipientActionVerb} tu documento" #: apps/remix/app/components/general/teams/team-email-dropdown.tsx #: apps/remix/app/components/general/document-signing/document-signing-field-container.tsx #: apps/remix/app/components/general/document-signing/document-signing-field-container.tsx -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx #: apps/remix/app/components/forms/avatar-image.tsx #: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx #: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx @@ -4710,11 +4855,11 @@ msgstr "Restablecer contraseña" msgid "Resetting Password..." msgstr "Restableciendo contraseña..." -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Resolve" msgstr "Resolver" -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Resolve payment" msgstr "Resolver pago" @@ -4754,7 +4899,7 @@ msgstr "Revocar" msgid "Revoke access" msgstr "Revocar acceso" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/tables/team-members-table.tsx #: apps/remix/app/components/tables/team-groups-table.tsx #: apps/remix/app/components/tables/organisation-members-table.tsx @@ -4764,7 +4909,6 @@ msgstr "Revocar acceso" #: apps/remix/app/components/dialogs/team-member-update-dialog.tsx #: apps/remix/app/components/dialogs/team-group-update-dialog.tsx #: apps/remix/app/components/dialogs/organisation-member-update-dialog.tsx -#: apps/remix/app/components/dialogs/organisation-member-invite-dialog.tsx msgid "Role" msgstr "Rol" @@ -4797,6 +4941,14 @@ msgstr "Guardar plantilla" msgid "Search" msgstr "Buscar" +#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx +msgid "Search and manage all organisations" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/claims.tsx +msgid "Search by claim ID or name" +msgstr "" + #: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx msgid "Search by document title" msgstr "Buscar por título del documento" @@ -4806,6 +4958,10 @@ msgstr "Buscar por título del documento" msgid "Search by name or email" msgstr "Buscar por nombre o correo electrónico" +#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx +msgid "Search by organisation ID, name, customer ID or owner email" +msgstr "" + #: apps/remix/app/components/general/document/document-search.tsx msgid "Search documents..." msgstr "Buscar documentos..." @@ -4834,6 +4990,14 @@ msgstr "Actividad de seguridad" msgid "Select" msgstr "Seleccionar" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Select a plan" +msgstr "" + +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Select a plan to continue" +msgstr "" + #: apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx msgid "Select a team to view its dashboard" msgstr "" @@ -4889,6 +5053,10 @@ msgstr "" msgid "Select passkey" msgstr "Seleccionar clave de acceso" +#: apps/remix/app/components/forms/document-preferences-form.tsx +msgid "Select signature types" +msgstr "" + #: apps/remix/app/components/dialogs/organisation-group-create-dialog.tsx msgid "Select the members to add to this group" msgstr "" @@ -5182,10 +5350,6 @@ msgstr "Firmando" msgid "Signing Certificate" msgstr "Certificado de Firma" -#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx -msgid "Signing certificate provided by" -msgstr "Certificado de firma proporcionado por" - #: packages/lib/server-only/document/send-completed-email.ts #: packages/lib/server-only/document/send-completed-email.ts msgid "Signing Complete!" @@ -5243,24 +5407,23 @@ msgstr "Algunos firmantes no han sido asignados a un campo de firma. Asigne al m #: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx #: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.public-profile.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.groups.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.groups.tsx -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/organisation-member-invites-table.tsx #: apps/remix/app/components/tables/organisation-member-invites-table.tsx #: apps/remix/app/components/tables/documents-table-action-dropdown.tsx #: apps/remix/app/components/tables/documents-table-action-button.tsx -#: apps/remix/app/components/general/billing-portal-button.tsx #: apps/remix/app/components/general/billing-plans.tsx -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx #: apps/remix/app/components/general/teams/team-email-usage.tsx #: apps/remix/app/components/general/teams/team-email-dropdown.tsx -#: apps/remix/app/components/general/teams/team-billing-portal-button.tsx #: apps/remix/app/components/general/organisations/organisation-invitations.tsx #: apps/remix/app/components/general/organisations/organisation-invitations.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-portal-button.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx #: apps/remix/app/components/general/document-signing/document-signing-auth-page.tsx #: apps/remix/app/components/general/document-signing/document-signing-auth-account.tsx #: apps/remix/app/components/general/document/document-page-view-dropdown.tsx @@ -5269,7 +5432,6 @@ msgstr "Algunos firmantes no han sido asignados a un campo de firma. Asigne al m #: apps/remix/app/components/general/document/document-audit-log-download-button.tsx #: apps/remix/app/components/general/direct-template/direct-template-signing-auth-page.tsx #: apps/remix/app/components/general/direct-template/direct-template-page.tsx -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx #: apps/remix/app/components/embed/embed-document-signing-page.tsx #: apps/remix/app/components/embed/embed-direct-template-client-page.tsx #: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx @@ -5279,8 +5441,6 @@ msgstr "Algunos firmantes no han sido asignados a un campo de firma. Asigne al m #: apps/remix/app/components/dialogs/template-create-dialog.tsx #: apps/remix/app/components/dialogs/team-email-delete-dialog.tsx #: apps/remix/app/components/dialogs/team-email-delete-dialog.tsx -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx #: apps/remix/app/components/dialogs/organisation-member-invite-dialog.tsx #: apps/remix/app/components/dialogs/document-resend-dialog.tsx #: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx @@ -5347,9 +5507,8 @@ msgid "Stats" msgstr "Estadísticas" #: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx #: apps/remix/app/components/tables/documents-table.tsx #: apps/remix/app/components/general/template/template-page-view-documents-table.tsx msgid "Status" @@ -5359,6 +5518,18 @@ msgstr "Estado" msgid "Step <0>{step} of {maxStep}" msgstr "Paso <0>{step} de {maxStep}" +#: apps/remix/app/components/tables/admin-organisations-table.tsx +msgid "Stripe" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Stripe customer created successfully" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Stripe Customer ID" +msgstr "" + #: packages/ui/primitives/template-flow/add-template-settings.tsx #: packages/ui/primitives/document-flow/add-subject.tsx msgid "Subject <0>(Optional)" @@ -5368,17 +5539,32 @@ msgstr "Asunto <0>(Opcional)" msgid "Subscribe" msgstr "" -#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Subscription" msgstr "Suscripción" -#: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx -msgid "Subscriptions" -msgstr "Suscripciones" +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Subscription claim created successfully." +msgstr "" + +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "Subscription claim deleted successfully." +msgstr "" + +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Subscription claim updated successfully." +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/claims.tsx +msgid "Subscription Claims" +msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/organisation-member-invites-table.tsx @@ -5440,8 +5626,8 @@ msgid "System Theme" msgstr "Tema del sistema" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/tables/organisation-teams-table.tsx -#: apps/remix/app/components/tables/organisation-pending-teams-table.tsx msgid "Team" msgstr "Equipo" @@ -5458,9 +5644,10 @@ msgstr "" msgid "Team Assignments" msgstr "" -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "Team checkout" -msgstr "Checkout del equipo" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Team Count" +msgstr "" #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx @@ -5570,6 +5757,10 @@ msgstr "Configuraciones del equipo" msgid "Team templates" msgstr "Plantillas del equipo" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Team url" +msgstr "" + #: apps/remix/app/components/forms/team-update-form.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx msgid "Team URL" @@ -5587,7 +5778,7 @@ msgstr "Equipos" msgid "Teams help you organize your work and collaborate with others. Create your first team to get started." msgstr "" -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Teams restricted" msgstr "Equipos restringidos" @@ -5764,6 +5955,12 @@ msgstr "" msgid "The organisation role that will be applied to all members in this group." msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "" +"The organisation you are looking for may have been removed, renamed or may have never\n" +" existed." +msgstr "" + #: apps/remix/app/routes/_authenticated+/_layout.tsx msgid "" "The organisation you are looking for may have been removed, renamed or may have never\n" @@ -5936,6 +6133,10 @@ msgstr "Esta acción es reversible, pero ten cuidado ya que la cuenta podría ve msgid "This can be overriden by setting the authentication requirements directly on each recipient in the next step." msgstr "Esto se puede anular configurando los requisitos de autenticación directamente en cada destinatario en el siguiente paso." +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "This claim is locked and cannot be deleted." +msgstr "" + #: packages/email/template-components/template-document-super-delete.tsx msgid "This document can not be recovered, if you would like to dispute the reason for future documents please contact support." msgstr "Este documento no se puede recuperar, si deseas impugnar la razón para documentos futuros, por favor contacta con el soporte." @@ -6020,6 +6221,10 @@ msgstr "Este campo no se puede modificar ni eliminar. Cuando comparta el enlace msgid "This is how the document will reach the recipients once the document is ready for signing." msgstr "Así es como el documento llegará a los destinatarios una vez que esté listo para firmarse." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "This is the claim that this organisation was initially created with. Any feature flag changes to this claim will be backported into this organisation." +msgstr "" + #: apps/remix/app/routes/_unauthenticated+/team.verify.transfer.token.tsx msgid "This link is invalid or has expired." msgstr "" @@ -6040,10 +6245,6 @@ msgstr "Esta clave de acceso ya ha sido registrada." msgid "This passkey is not configured for this application. Please login and add one in the user settings." msgstr "Esta clave de acceso no está configurada para esta aplicación. Por favor, inicia sesión y añade una en la configuración del usuario." -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "This price includes minimum 5 seats." -msgstr "Este precio incluye un mínimo de 5 asientos." - #: packages/ui/primitives/document-flow/add-fields.tsx msgid "This recipient can no longer be modified as they have signed a field, or completed the document." msgstr "Este destinatario ya no puede ser modificado ya que ha firmado un campo o completado el documento." @@ -6075,14 +6276,9 @@ msgstr "Este token es inválido o ha expirado. Por favor, contacta a tu equipo p #: apps/remix/app/components/forms/team-update-form.tsx #: apps/remix/app/components/forms/organisation-update-form.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx -#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "This URL is already in use." msgstr "Esta URL ya está en uso." -#: apps/remix/app/components/forms/signup.tsx -msgid "This username has already been taken" -msgstr "Este nombre de usuario ya ha sido tomado" - #: packages/ui/components/document/document-email-checkboxes.tsx msgid "This will be sent to all recipients if a pending document has been deleted." msgstr "Esto se enviará a todos los destinatarios si un documento pendiente ha sido eliminado." @@ -6095,6 +6291,10 @@ msgstr "Esto se enviará a todos los destinatarios una vez que el documento est msgid "This will be sent to the document owner once the document has been fully completed." msgstr "Esto se enviará al propietario del documento una vez que el documento se haya completado por completo." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "This will ONLY backport feature flags which are set to true, anything disabled in the initial claim will not be backported" +msgstr "" + #: packages/ui/components/recipient/recipient-action-auth-select.tsx msgid "This will override any global settings." msgstr "Esto anulará cualquier configuración global." @@ -6385,20 +6585,27 @@ msgstr "Incompleto" msgid "Unknown" msgstr "Desconocido" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx -msgid "Unpaid" -msgstr "No pagado" +#: apps/remix/app/components/tables/admin-claims-table.tsx +msgid "Unlimited" +msgstr "" + +#: packages/lib/types/subscription.ts +msgid "Unlimited documents, API and more" +msgstr "" #: apps/remix/app/components/dialogs/team-group-create-dialog.tsx msgid "Untitled Group" msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx #: apps/remix/app/components/forms/public-profile-form.tsx #: apps/remix/app/components/forms/document-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx #: apps/remix/app/components/dialogs/team-member-update-dialog.tsx #: apps/remix/app/components/dialogs/team-group-update-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx @@ -6413,6 +6620,14 @@ msgstr "Actualizar" msgid "Update Banner" msgstr "Actualizar banner" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx +msgid "Update Billing" +msgstr "" + +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Update Claim" +msgstr "" + #: apps/remix/app/components/forms/organisation-update-form.tsx msgid "Update organisation" msgstr "" @@ -6445,6 +6660,10 @@ msgstr "Actualizar destinatario" msgid "Update role" msgstr "Actualizar rol" +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Update Subscription Claim" +msgstr "" + #: apps/remix/app/components/forms/team-update-form.tsx msgid "Update team" msgstr "Actualizar equipo" @@ -6524,7 +6743,7 @@ msgstr "Subir firma" msgid "Upload Template Document" msgstr "Cargar Documento Plantilla" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Upload your brand logo (max 5MB, JPG, PNG, or WebP)" msgstr "Carga el logo de tu marca (máx 5MB, JPG, PNG o WebP)" @@ -6572,10 +6791,6 @@ msgstr "Usuario" msgid "User has no password." msgstr "El usuario no tiene contraseña." -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx -msgid "User ID" -msgstr "ID de Usuario" - #: apps/remix/app/components/dialogs/admin-user-enable-dialog.tsx #: apps/remix/app/components/dialogs/admin-user-disable-dialog.tsx #: apps/remix/app/components/dialogs/admin-user-delete-dialog.tsx @@ -6590,10 +6805,6 @@ msgstr "¡Los perfiles de usuario están aquí!" msgid "User with this email already exists. Please use a different email address." msgstr "Un usuario con este correo electrónico ya existe. Por favor, use una dirección de correo diferente." -#: apps/remix/app/components/forms/signup.tsx -msgid "Username can only container alphanumeric characters and dashes." -msgstr "El nombre de usuario solo puede contener caracteres alfanuméricos y guiones." - #: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx msgid "Users" msgstr "Usuarios" @@ -6641,7 +6852,7 @@ msgstr "Verifica tu dirección de correo electrónico del equipo" msgid "Version History" msgstr "Historial de Versiones" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx #: apps/remix/app/components/tables/inbox-table.tsx #: apps/remix/app/components/tables/documents-table-action-dropdown.tsx #: apps/remix/app/components/tables/documents-table-action-button.tsx @@ -6709,6 +6920,10 @@ msgstr "Ver más" msgid "View Original Document" msgstr "Ver Documento Original" +#: apps/remix/app/components/tables/admin-organisations-table.tsx +msgid "View owner" +msgstr "" + #: packages/email/template-components/template-document-self-signed.tsx msgid "View plans" msgstr "Ver planes" @@ -6778,9 +6993,8 @@ msgstr "¿Quieres tu propio perfil público?" msgid "Warning: Assistant as last signer" msgstr "" -#: apps/remix/app/components/general/billing-portal-button.tsx -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx -#: apps/remix/app/components/general/teams/team-billing-portal-button.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-portal-button.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "We are unable to proceed to the billing portal at this time. Please try again, or contact support." msgstr "No podemos proceder al portal de facturación en este momento. Por favor, inténtalo de nuevo o contacta con soporte." @@ -6792,10 +7006,19 @@ msgstr "No podemos eliminar esta clave de acceso en este momento. Por favor, int msgid "We are unable to update this passkey at the moment. Please try again later." msgstr "No podemos actualizar esta clave de acceso en este momento. Por favor, inténtalo de nuevo más tarde." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "We couldn't create a Stripe customer. Please try again." +msgstr "" + #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx msgid "We couldn't update the group. Please try again." msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "We couldn't update the organisation. Please try again." +msgstr "" + #: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx msgid "We encountered an error while removing the direct template link. Please try again later." msgstr "Encontramos un error al eliminar el enlace directo de la plantilla. Por favor, inténtalo de nuevo más tarde." @@ -6829,10 +7052,6 @@ msgstr "Encontramos un error desconocido al intentar crear un equipo. Por favor, msgid "We encountered an unknown error while attempting to delete it. Please try again later." msgstr "Encontramos un error desconocido al intentar eliminarlo. Por favor, inténtalo de nuevo más tarde." -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx -msgid "We encountered an unknown error while attempting to delete the pending team. Please try again later." -msgstr "Encontramos un error desconocido al intentar eliminar el equipo pendiente. Por favor, inténtalo de nuevo más tarde." - #: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx msgid "We encountered an unknown error while attempting to delete this organisation. Please try again later." msgstr "" @@ -6943,10 +7162,6 @@ msgstr "Encontramos un error desconocido al intentar actualizar su perfil. Por f msgid "We have sent a confirmation email for verification." msgstr "Hemos enviado un correo electrónico de confirmación para la verificación." -#: apps/remix/app/components/forms/signup.tsx -msgid "We need a username to create your profile" -msgstr "Necesitamos un nombre de usuario para crear tu perfil" - #: apps/remix/app/components/forms/signup.tsx msgid "We need your signature to sign documents" msgstr "Necesitamos su firma para firmar documentos" @@ -6959,10 +7174,6 @@ msgstr "No pudimos copiar el token en tu portapapeles. Por favor, inténtalo de msgid "We were unable to copy your recovery code to your clipboard. Please try again." msgstr "No pudimos copiar tu código de recuperación en tu portapapeles. Por favor, inténtalo de nuevo." -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "We were unable to create a checkout session. Please try again, or contact support" -msgstr "No pudimos crear una sesión de pago. Por favor, inténtalo de nuevo o contacta con soporte" - #: apps/remix/app/components/forms/signup.tsx msgid "We were unable to create your account. Please review the information you provided and try again." msgstr "No pudimos crear su cuenta. Revise la información que proporcionó e inténtelo de nuevo." @@ -6992,7 +7203,7 @@ msgstr "No pudimos configurar la autenticación de dos factores para tu cuenta. msgid "We were unable to submit this document at this time. Please try again later." msgstr "No pudimos enviar este documento en este momento. Por favor, inténtalo de nuevo más tarde." -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx msgid "We were unable to update your branding preferences at this time, please try again later" msgstr "No pudimos actualizar tus preferencias de marca en este momento, por favor intenta de nuevo más tarde" @@ -7065,10 +7276,6 @@ msgstr "URL del Webhook" msgid "Webhooks" msgstr "Webhooks" -#: apps/remix/app/components/general/billing-plans.tsx -msgid "Weekly" -msgstr "" - #: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx msgid "Welcome" msgstr "Bienvenido" @@ -7114,6 +7321,10 @@ msgstr "Cuando utilice nuestra plataforma para colocar su firma electrónica en msgid "While waiting for them to do so you can create your own Documenso account and get started with document signing right away." msgstr "Mientras esperas a que ellos lo hagan, puedes crear tu propia cuenta de Documenso y comenzar a firmar documentos de inmediato." +#: packages/lib/types/subscription.ts +msgid "Whitelabeling, unlimited members and more" +msgstr "" + #: apps/remix/app/components/dialogs/document-resend-dialog.tsx msgid "Who do you want to remind?" msgstr "¿A quién deseas recordar?" @@ -7131,6 +7342,7 @@ msgid "Write about yourself" msgstr "Escribe sobre ti mismo" #: apps/remix/app/components/general/billing-plans.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Yearly" msgstr "Anual" @@ -7200,6 +7412,10 @@ msgstr "Estás a punto de revocar el acceso para el equipo <0>{0} ({1}) para msgid "You are about to send this document to the recipients. Are you sure you want to continue?" msgstr "Está a punto de enviar este documento a los destinatarios. ¿Está seguro de que desea continuar?" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "You are currently on the <0>Free Plan." +msgstr "" + #: apps/remix/app/components/dialogs/team-member-update-dialog.tsx msgid "You are currently updating <0>{memberName}." msgstr "" @@ -7306,12 +7522,12 @@ msgstr "No puede cargar documentos en este momento." msgid "You cannot upload encrypted PDFs" msgstr "No puedes subir PDFs encriptados" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx -msgid "You currently have no access to any teams within this organisation. Please contact your organisation to request access." +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "You currently have an inactive <0>{currentProductName} subscription" msgstr "" -#: apps/remix/app/components/general/billing-portal-button.tsx -msgid "You do not currently have a customer record, this should not happen. Please contact support for assistance." +#: apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx +msgid "You currently have no access to any teams within this organisation. Please contact your organisation to request access." msgstr "" #: apps/remix/app/components/forms/token.tsx @@ -7346,10 +7562,6 @@ msgstr "Te han invitado a unirte a {0} en Documenso" msgid "You have been invited to join the following organisation" msgstr "" -#: packages/email/templates/organisation-invite.tsx -msgid "You have been invited to join the following team" -msgstr "Te han invitado a unirte al siguiente equipo" - #: packages/lib/server-only/recipient/set-document-recipients.ts #: packages/lib/server-only/recipient/delete-document-recipient.ts msgid "You have been removed from a document" @@ -7383,6 +7595,10 @@ msgstr "Aún no has creado ni recibido documentos. Para crear un documento, por msgid "You have reached the maximum limit of {0} direct templates. <0>Upgrade your account to continue!" msgstr "Has alcanzado el límite máximo de {0} plantillas directas. <0>¡Actualiza tu cuenta para continuar!" +#: apps/remix/app/components/dialogs/team-create-dialog.tsx +msgid "You have reached the maximum number of teams for your plan. Please contact sales at <0>support@documenso.com if you would like to adjust your plan." +msgstr "" + #: apps/remix/app/components/general/document/document-upload.tsx msgid "You have reached your document limit for this month. Please upgrade your plan." msgstr "Ha alcanzado su límite de documentos para este mes. Por favor, actualice su plan." @@ -7482,10 +7698,6 @@ msgstr "Debes iniciar sesión para ver esta página." msgid "You need to setup 2FA to mark this document as viewed." msgstr "Debes configurar 2FA para marcar este documento como visto." -#: apps/remix/app/components/forms/signup.tsx -msgid "You will get notified & be able to set up your documenso public profile when we launch the feature." -msgstr "Recibirás una notificación y podrás configurar tu perfil público de Documenso cuando lanzemos la función." - #: apps/remix/app/components/forms/2fa/enable-authenticator-app-dialog.tsx msgid "You will now be required to enter a code from your authenticator app when signing in." msgstr "Ahora se te pedirá que ingreses un código de tu aplicación de autenticador al iniciar sesión." @@ -7506,11 +7718,11 @@ msgstr "Tu avatar ha sido actualizado con éxito." msgid "Your banner has been updated successfully." msgstr "Tu banner ha sido actualizado con éxito." -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Your brand website URL" msgstr "La URL de tu sitio web de marca" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx msgid "Your branding preferences have been updated" msgstr "Tus preferencias de marca han sido actualizadas" @@ -7522,6 +7734,18 @@ msgstr "Tu envío masivo ha sido iniciado. Recibirás una notificación por corr msgid "Your bulk send operation for template \"{templateName}\" has completed." msgstr "Tu operación de envío masivo para la plantilla \"{templateName}\" ha sido completada." +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "Your current {currentProductName} plan is past due. Please update your payment information." +msgstr "" + +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "Your current plan is inactive." +msgstr "" + +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "Your current plan is past due." +msgstr "" + #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx msgid "Your direct signing templates" msgstr "Tus {0} plantillas de firma directa" @@ -7611,7 +7835,7 @@ msgstr "Tu contraseña ha sido actualizada con éxito." msgid "Your password has been updated." msgstr "Tu contraseña ha sido actualizada." -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Your payment for teams is overdue. Please settle the payment to avoid any service disruptions." msgstr "Tu pago por equipos está vencido. Por favor, salda el pago para evitar interrupciones en el servicio." diff --git a/packages/lib/translations/fr/web.po b/packages/lib/translations/fr/web.po index de91aa3aa..baf475531 100644 --- a/packages/lib/translations/fr/web.po +++ b/packages/lib/translations/fr/web.po @@ -73,11 +73,6 @@ msgstr "{0, plural, one {# caractère au-dessus de la limite} other {# caractèr msgid "{0, plural, one {# recipient} other {# recipients}}" msgstr "{0, plural, one {# destinataire} other {# destinataires}}" -#. placeholder {0}: row.original.quantity -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx -msgid "{0, plural, one {# Seat} other {# Seats}}" -msgstr "{0, plural, one {# siège} other {# sièges}}" - #. placeholder {0}: org.teams.length #: apps/remix/app/routes/_authenticated+/dashboard.tsx msgid "{0, plural, one {# team} other {# teams}}" @@ -132,16 +127,6 @@ msgstr "{0} vous a invité à {recipientActionVerb} le document \"{1}\"." msgid "{0} invited you to {recipientActionVerb} a document" msgstr "{0} vous a invité à {recipientActionVerb} un document" -#. placeholder {0}: memberName || memberEmail -#: packages/email/templates/team-join.tsx -msgid "{0} joined the team {teamName} on Documenso" -msgstr "{0} a rejoint l'équipe {teamName} sur Documenso" - -#. placeholder {0}: memberName || memberEmail -#: packages/email/templates/team-leave.tsx -msgid "{0} left the team {teamName} on Documenso" -msgstr "{0} a quitté l'équipe {teamName} sur Documenso" - #. placeholder {0}: remaining.documents #. placeholder {1}: quota.documents #: apps/remix/app/components/general/document/document-upload.tsx @@ -223,14 +208,6 @@ msgstr "{inviterName} représentant \"{teamName}\" vous a invité à {0}<0/>\"{d msgid "{inviterName} on behalf of \"{teamName}\" has invited you to {action} {documentName}" msgstr "{inviterName} représentant \"{teamName}\" vous a invité à {action} {documentName}" -#: packages/email/templates/team-join.tsx -msgid "{memberEmail} joined the following team" -msgstr "{memberEmail} a rejoint l'équipe suivante" - -#: packages/email/templates/team-leave.tsx -msgid "{memberEmail} left the following team" -msgstr "{memberEmail} a quitté l'équipe suivante" - #: packages/lib/utils/document-audit-logs.ts msgid "{prefix} added a field" msgstr "{prefix} a ajouté un champ" @@ -484,6 +461,14 @@ msgstr "<0>Vous êtes sur le point de terminer la signature de \"<1>{documentTit msgid "<0>You are about to complete viewing \"<1>{documentTitle}\".<2/> Are you sure?" msgstr "<0>Vous êtes sur le point de terminer la visualisation de \"<1>{documentTitle}\".<2/> Êtes-vous sûr ?" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "0 Free organisations left" +msgstr "" + +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "1 Free organisations left" +msgstr "" + #: apps/remix/app/components/forms/token.tsx msgid "1 month" msgstr "1 mois" @@ -510,6 +495,7 @@ msgid "404 Organisation group not found" msgstr "" #: apps/remix/app/routes/_authenticated+/_layout.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "404 Organisation not found" msgstr "" @@ -522,6 +508,14 @@ msgstr "404 Profil non trouvé" msgid "404 Team not found" msgstr "404 Équipe non trouvée" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "5 documents a month" +msgstr "" + +#: packages/lib/types/subscription.ts +msgid "5 Documents a month" +msgstr "" + #: apps/remix/app/components/general/generic-error-layout.tsx msgid "500 Internal Server Error" msgstr "" @@ -566,9 +560,29 @@ msgstr "Un champ a été mis à jour" msgid "A means to print or download documents for your records" msgstr "Un moyen d'imprimer ou de télécharger des documents pour vos dossiers" -#: packages/lib/jobs/definitions/emails/send-team-member-joined-email.handler.ts -msgid "A new member has joined your team" -msgstr "Un nouveau membre a rejoint votre équipe" +#: packages/email/templates/organisation-join.tsx +msgid "A member has joined your organisation on Documenso" +msgstr "" + +#: packages/lib/jobs/definitions/emails/send-organisation-member-left-email.handler.ts +msgid "A member has left your organisation" +msgstr "" + +#: packages/email/templates/organisation-leave.tsx +msgid "A member has left your organisation {organisationName}" +msgstr "" + +#: packages/email/templates/organisation-leave.tsx +msgid "A member has left your organisation on Documenso" +msgstr "" + +#: packages/lib/jobs/definitions/emails/send-organisation-member-joined-email.handler.ts +msgid "A new member has joined your organisation" +msgstr "" + +#: packages/email/templates/organisation-join.tsx +msgid "A new member has joined your organisation {organisationName}" +msgstr "" #: apps/remix/app/components/forms/token.tsx msgid "A new token was created successfully." @@ -608,19 +622,6 @@ msgstr "Un secret qui sera envoyé à votre URL afin que vous puissiez vérifier msgid "A stable internet connection" msgstr "Une connexion Internet stable" -#: packages/email/templates/team-join.tsx -msgid "A team member has joined a team on Documenso" -msgstr "Un membre de l'équipe a rejoint une équipe sur Documenso" - -#. placeholder {0}: team.name -#: packages/lib/jobs/definitions/emails/send-team-member-left-email.handler.ts -msgid "A team member has left {0}" -msgstr "Un membre de l'équipe a quitté {0}" - -#: packages/email/templates/team-leave.tsx -msgid "A team member has left a team on Documenso" -msgstr "Un membre de l'équipe a quitté une équipe sur Documenso" - #: packages/email/templates/team-delete.tsx #: packages/email/templates/team-delete.tsx msgid "A team you were a part of has been deleted" @@ -630,8 +631,11 @@ msgstr "Une équipe dont vous faisiez partie a été supprimée" msgid "A unique URL to access your profile" msgstr "Une URL unique pour accéder à votre profil" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "A unique URL to identify the organisation" +msgstr "" + #: apps/remix/app/components/forms/organisation-update-form.tsx -#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "A unique URL to identify your organisation" msgstr "" @@ -651,8 +655,8 @@ msgid "Accept" msgstr "Accepter" #: packages/email/templates/organisation-invite.tsx -msgid "Accept invitation to join a team on Documenso" -msgstr "Accepter l'invitation à rejoindre une équipe sur Documenso" +msgid "Accept invitation to join an organisation on Documenso" +msgstr "" #: packages/email/templates/confirm-team-email.tsx msgid "Accept team email request for {teamName} on Documenso" @@ -720,11 +724,12 @@ msgstr "Action" #: apps/remix/app/components/tables/organisation-groups-table.tsx #: apps/remix/app/components/tables/inbox-table.tsx #: apps/remix/app/components/tables/documents-table.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx #: apps/remix/app/components/general/template/template-page-view-documents-table.tsx msgid "Actions" msgstr "Actions" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.teams.tsx #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.members.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx msgid "Active" @@ -865,11 +870,7 @@ msgstr "Ajouter les personnes qui signeront le document." msgid "Add the recipients to create the document with" msgstr "Ajouter les destinataires pour créer le document avec" -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "Adding and removing seats will adjust your invoice accordingly." -msgstr "Ajouter et supprimer des sièges ajustera votre facture en conséquence." - -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Additional brand information to display at the bottom of emails" msgstr "Informations supplémentaires sur la marque à afficher en bas des e-mails" @@ -886,6 +887,10 @@ msgstr "Actions administratives" msgid "Admin panel" msgstr "Panneau d'administration" +#: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx +msgid "Admin Panel" +msgstr "" + #: packages/ui/primitives/template-flow/add-template-settings.tsx #: packages/ui/primitives/document-flow/add-settings.tsx msgid "Advanced Options" @@ -962,6 +967,10 @@ msgstr "" msgid "Allowed Signature Types" msgstr "" +#: apps/remix/app/components/tables/admin-claims-table.tsx +msgid "Allowed teams" +msgstr "" + #: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx msgid "Allows authenticating using biometrics, password managers, hardware keys, etc." msgstr "Permet d'authentifier en utilisant des biométries, des gestionnaires de mots de passe, des clés matérielles, etc." @@ -970,7 +979,7 @@ msgstr "Permet d'authentifier en utilisant des biométries, des gestionnaires de msgid "Already have an account? <0>Sign in instead" msgstr "Vous avez déjà un compte ? <0>Connectez-vous plutôt" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx msgid "Amount" msgstr "Montant" @@ -991,6 +1000,8 @@ msgid "An email containing an invitation will be sent to each member." msgstr "Un e-mail contenant une invitation sera envoyé à chaque membre." #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/general/claim-account.tsx #: apps/remix/app/components/forms/token.tsx #: apps/remix/app/components/forms/signup.tsx @@ -1230,6 +1241,10 @@ msgstr "Approval en cours" msgid "Are you sure you want to complete the document? This action cannot be undone. Please ensure that you have completed prefilling all relevant fields before proceeding." msgstr "" +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "Are you sure you want to delete the following claim?" +msgstr "" + #: apps/remix/app/components/dialogs/token-delete-dialog.tsx msgid "Are you sure you want to delete this token?" msgstr "Êtes-vous sûr de vouloir supprimer ce token ?" @@ -1340,9 +1355,9 @@ msgid "Awaiting email confirmation" msgstr "En attente de confirmation par e-mail" #: apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx -#: apps/remix/app/components/forms/signup.tsx #: apps/remix/app/components/dialogs/team-member-create-dialog.tsx #: apps/remix/app/components/dialogs/team-group-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Back" msgstr "Retour" @@ -1367,29 +1382,17 @@ msgstr "Codes de sauvegarde" msgid "Banner Updated" msgstr "Bannière mise à jour" -#: apps/remix/app/components/forms/signup.tsx -msgid "Basic details" -msgstr "Détails de base" - #: packages/email/template-components/template-confirmation-email.tsx msgid "Before you get started, please confirm your email address by clicking the button below:" msgstr "Avant de commencer, veuillez confirmer votre adresse e-mail en cliquant sur le bouton ci-dessous :" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings._layout.tsx -#: apps/remix/app/routes/_authenticated+/settings+/billing.tsx -#: apps/remix/app/components/general/settings-nav-mobile.tsx -#: apps/remix/app/components/general/settings-nav-desktop.tsx #: apps/remix/app/components/general/teams/team-settings-nav-mobile.tsx #: apps/remix/app/components/general/teams/team-settings-nav-desktop.tsx msgid "Billing" msgstr "Facturation" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx -#: apps/remix/app/routes/_authenticated+/settings+/billing.tsx -msgid "Billing has been moved to organisations" -msgstr "" - #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx msgid "Black" msgstr "Noir" @@ -1398,12 +1401,13 @@ msgstr "Noir" msgid "Blue" msgstr "Bleu" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx msgid "Branding Preferences" msgstr "Préférences de branding" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx msgid "Branding preferences updated" msgstr "Préférences de branding mises à jour" @@ -1512,7 +1516,7 @@ msgstr "" #: apps/remix/app/components/dialogs/team-email-add-dialog.tsx #: apps/remix/app/components/dialogs/team-delete-dialog.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx +#: apps/remix/app/components/dialogs/team-create-dialog.tsx #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx #: apps/remix/app/components/dialogs/organisation-member-update-dialog.tsx @@ -1523,9 +1527,13 @@ msgstr "" #: apps/remix/app/components/dialogs/organisation-group-create-dialog.tsx #: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx #: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx #: apps/remix/app/components/dialogs/document-resend-dialog.tsx #: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx #: apps/remix/app/components/dialogs/document-delete-dialog.tsx +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx #: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx #: packages/ui/primitives/signature-pad/signature-pad-dialog.tsx #: packages/ui/primitives/document-flow/send-document-action-dialog.tsx @@ -1574,7 +1582,7 @@ msgstr "Case à cocher" msgid "Checkbox values" msgstr "Valeurs de la case à cocher" -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Checkout" msgstr "Passer à la caisse" @@ -1598,13 +1606,9 @@ msgstr "Choisissez..." msgid "Claim account" msgstr "Revendiquer le compte" -#: apps/remix/app/components/forms/signup.tsx -msgid "Claim username" -msgstr "Revendiquer le nom d'utilisateur" - -#: apps/remix/app/components/forms/signup.tsx -msgid "Claim your username now" -msgstr "Revendiquer votre nom d'utilisateur maintenant" +#: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx +msgid "Claims" +msgstr "" #: apps/remix/app/components/dialogs/template-use-dialog.tsx msgid "Clear file" @@ -1660,6 +1664,10 @@ msgstr "Cliquez pour insérer un champ" msgid "Close" msgstr "Fermer" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Compare all plans and features in detail" +msgstr "" + #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx #: apps/remix/app/components/forms/signup.tsx @@ -1782,6 +1790,10 @@ msgstr "Consentement aux transactions électroniques" msgid "Contact Information" msgstr "Coordonnées" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Contact sales here" +msgstr "" + #: apps/remix/app/routes/_authenticated+/admin+/site-settings.tsx msgid "Content" msgstr "Contenu" @@ -1793,6 +1805,7 @@ msgstr "Contenu" #: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx #: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx #: packages/ui/primitives/document-flow/document-flow-root.tsx msgid "Continue" @@ -1892,6 +1905,7 @@ msgstr "Copier le token" #: apps/remix/app/routes/_profile+/_layout.tsx #: apps/remix/app/components/dialogs/webhook-create-dialog.tsx #: apps/remix/app/components/dialogs/organisation-group-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Create" msgstr "Créer" @@ -1928,6 +1942,14 @@ msgstr "Créer en tant que brouillon" msgid "Create as pending" msgstr "Créer comme en attente" +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Create claim" +msgstr "" + +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Create Claim" +msgstr "" + #: apps/remix/app/components/dialogs/template-direct-link-dialog-wrapper.tsx msgid "Create Direct Link" msgstr "Créer un lien direct" @@ -1962,18 +1984,26 @@ msgstr "Créer un automatiquement" msgid "Create organisation" msgstr "" -#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx -msgid "Create Organisation" -msgstr "" - #: apps/remix/app/components/general/menu-switcher.tsx -msgid "Create Organization" +msgid "Create Organisation" msgstr "" #: apps/remix/app/components/dialogs/template-use-dialog.tsx msgid "Create signing links" msgstr "Créer des liens de signature" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Create Stripe customer" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Create subscription" +msgstr "" + +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Create Subscription Claim" +msgstr "" + #: apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx @@ -2025,7 +2055,7 @@ msgid "Created" msgstr "Créé" #: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Created At" msgstr "Créé le" @@ -2034,7 +2064,6 @@ msgid "Created by" msgstr "Créé par" #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx -#: apps/remix/app/components/tables/organisation-pending-teams-table.tsx msgid "Created on" msgstr "Créé le" @@ -2065,12 +2094,16 @@ msgstr "Destinataires actuels :" msgid "Currently all organisation members can access this team" msgstr "" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups._index.tsx -msgid "Custom Organisation Groups" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx +msgid "Currently branding can only be configured for Teams and above plans." msgstr "" -#: apps/remix/app/components/general/billing-plans.tsx -msgid "Daily" +#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx +msgid "Currently branding can only be configured on the organisation level." +msgstr "" + +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups._index.tsx +msgid "Custom Organisation Groups" msgstr "" #: apps/remix/app/components/general/app-command-menu.tsx @@ -2130,16 +2163,20 @@ msgstr "supprimer" #: apps/remix/app/components/tables/organisation-teams-table.tsx #: apps/remix/app/components/tables/organisation-groups-table.tsx #: apps/remix/app/components/tables/documents-table-action-dropdown.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx #: apps/remix/app/components/general/document/document-page-view-dropdown.tsx #: apps/remix/app/components/dialogs/webhook-delete-dialog.tsx #: apps/remix/app/components/dialogs/token-delete-dialog.tsx #: apps/remix/app/components/dialogs/template-delete-dialog.tsx #: apps/remix/app/components/dialogs/team-group-delete-dialog.tsx #: apps/remix/app/components/dialogs/team-delete-dialog.tsx +#: apps/remix/app/components/dialogs/team-delete-dialog.tsx #: apps/remix/app/components/dialogs/organisation-member-delete-dialog.tsx #: apps/remix/app/components/dialogs/organisation-group-delete-dialog.tsx #: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx #: apps/remix/app/components/dialogs/document-delete-dialog.tsx +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx msgid "Delete" msgstr "Supprimer" @@ -2179,7 +2216,6 @@ msgid "Delete Document" msgstr "Supprimer le document" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.general.tsx -#: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx msgid "Delete organisation" msgstr "" @@ -2195,8 +2231,11 @@ msgstr "" msgid "Delete passkey" msgstr "Supprimer la clé d'accès" +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "Delete Subscription Claim" +msgstr "" + #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx -#: apps/remix/app/components/dialogs/team-delete-dialog.tsx msgid "Delete team" msgstr "Supprimer l'équipe" @@ -2620,7 +2659,7 @@ msgstr "Documents consultés" msgid "Don't have an account? <0>Sign up" msgstr "Vous n'avez pas de compte? <0>Inscrivez-vous" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx #: apps/remix/app/components/tables/documents-table-action-dropdown.tsx #: apps/remix/app/components/tables/documents-table-action-button.tsx #: apps/remix/app/components/general/document/document-page-view-dropdown.tsx @@ -2675,7 +2714,7 @@ msgstr "Liste déroulante" msgid "Dropdown options" msgstr "Options de liste déroulante" -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Due to an unpaid invoice, your team has been restricted. Please settle the payment to restore full access to your team." msgstr "En raison d'une facture impayée, votre équipe a été restreinte. Veuillez régler le paiement pour rétablir l'accès complet à votre équipe." @@ -2718,6 +2757,7 @@ msgstr "Divulgation de signature électronique" #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-dashboard-users-table.tsx #: apps/remix/app/components/general/document-signing/document-signing-email-field.tsx @@ -2790,6 +2830,10 @@ msgstr "La vérification par email a été supprimée" msgid "Email verification has been resent" msgstr "La vérification par email a été renvoyée" +#: packages/lib/types/subscription.ts +msgid "Embedding, 5 members included and more" +msgstr "" + #: packages/ui/primitives/document-flow/add-fields.tsx msgid "Empty field" msgstr "Champ vide" @@ -2820,7 +2864,7 @@ msgstr "Activer le Compte" msgid "Enable Authenticator App" msgstr "Activer l'application Authenticator" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Enable custom branding for all documents in this team." msgstr "Activer la personnalisation de la marque pour tous les documents de cette équipe." @@ -2849,11 +2893,11 @@ msgstr "Activer le compte permet à l'utilisateur de pouvoir utiliser le compte msgid "Enclosed Document" msgstr "Document joint" -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx -msgid "Ends On" -msgstr "Se termine le" +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Enter claim name" +msgstr "" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Enter your brand details" msgstr "Entrez les informations de votre marque" @@ -2873,7 +2917,12 @@ msgstr "Entrez votre nom" msgid "Enter your text here" msgstr "Entrez votre texte ici" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Enterprise" +msgstr "" + #: apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx #: apps/remix/app/components/general/verify-email-banner.tsx #: apps/remix/app/components/general/template/template-edit-form.tsx @@ -2948,6 +2997,14 @@ msgstr "Expire le {0}" msgid "External ID" msgstr "ID externe" +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Failed to create subscription claim." +msgstr "" + +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "Failed to delete subscription claim." +msgstr "" + #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx msgid "Failed to reseal document" msgstr "Échec du reseal du document" @@ -2960,6 +3017,10 @@ msgstr "Échec de l'enregistrement des paramètres." msgid "Failed to update recipient" msgstr "Échec de la mise à jour du destinataire" +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Failed to update subscription claim." +msgstr "" + #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx msgid "Failed to update webhook" msgstr "Échec de la mise à jour du webhook" @@ -2968,6 +3029,12 @@ msgstr "Échec de la mise à jour du webhook" msgid "Failed: {failedCount}" msgstr "Échoués : {failedCount}" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Feature Flags" +msgstr "" + #: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx msgid "Field character limit" msgstr "Limite de caractères du champ" @@ -3019,6 +3086,10 @@ msgstr "Le fichier ne peut pas dépasser {APP_DOCUMENT_UPLOAD_SIZE_LIMIT} Mo" msgid "File size exceeds the limit of {APP_DOCUMENT_UPLOAD_SIZE_LIMIT} MB" msgstr "La taille du fichier dépasse la limite de {APP_DOCUMENT_UPLOAD_SIZE_LIMIT} MB" +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Fill in the details to create a new subscription claim." +msgstr "" + #: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx #: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx #: packages/ui/primitives/document-flow/field-items-advanced-settings/name-field.tsx @@ -3036,6 +3107,10 @@ msgstr "Pour toute question concernant cette divulgation, les signatures électr msgid "For each recipient, provide their email (required) and name (optional) in separate columns. Download the template CSV below for the correct format." msgstr "Pour chaque destinataire, fournissez son e-mail (obligatoire) et son nom (facultatif) dans des colonnes séparées. Téléchargez le modèle CSV ci-dessous pour obtenir le format requis." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "For example, if the claim has a new flag \"FLAG_1\" set to true, then this organisation will get that flag added." +msgstr "" + #: packages/lib/server-only/auth/send-forgot-password.ts msgid "Forgot Password?" msgstr "Mot de passe oublié ?" @@ -3046,6 +3121,10 @@ msgstr "Mot de passe oublié ?" msgid "Forgot your password?" msgstr "Vous avez oublié votre mot de passe ?" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Free" +msgstr "" + #: packages/ui/primitives/document-flow/types.ts msgid "Free Signature" msgstr "Signature gratuite" @@ -3079,6 +3158,7 @@ msgid "Global recipient action authentication" msgstr "Authentification d'action de destinataire globale" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "Go back" msgstr "" @@ -3193,7 +3273,6 @@ msgid "Here you can manage your password and security settings." msgstr "Ici, vous pouvez gérer votre mot de passe et vos paramètres de sécurité." #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx -#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx msgid "Here you can set preferences and defaults for branding." msgstr "Ici, vous pouvez définir des préférences et des valeurs par défaut pour le branding." @@ -3260,6 +3339,14 @@ msgstr "Je suis le propriétaire de ce document" msgid "I'm sure! Delete it" msgstr "Je suis sûr ! Supprimez-le" +#: apps/remix/app/components/tables/admin-claims-table.tsx +msgid "ID" +msgstr "" + +#: apps/remix/app/components/tables/admin-claims-table.tsx +msgid "ID copied to clipboard" +msgstr "" + #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx msgid "If you do not want to use the authenticator prompted, you can close it, which will then display the next available authenticator." msgstr "Si vous ne souhaitez pas utiliser l'authentificateur proposé, vous pouvez le fermer, ce qui affichera l'authentificateur suivant disponible." @@ -3298,6 +3385,7 @@ msgstr "Hériter de la méthode d'authentification" #: apps/remix/app/components/forms/document-preferences-form.tsx #: apps/remix/app/components/forms/document-preferences-form.tsx #: apps/remix/app/components/forms/document-preferences-form.tsx +#: apps/remix/app/components/forms/document-preferences-form.tsx msgid "Inherit from organisation" msgstr "" @@ -3305,6 +3393,11 @@ msgstr "" msgid "Inherit organisation members" msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Inherited subscription claim" +msgstr "" + #: apps/remix/app/components/general/document-signing/document-signing-initials-field.tsx #: packages/ui/primitives/document-flow/types.ts msgid "Initials" @@ -3385,7 +3478,7 @@ msgstr "" msgid "Invited At" msgstr "Invité à" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx msgid "Invoice" msgstr "Facture" @@ -3479,7 +3572,7 @@ msgstr "Dernière utilisation" msgid "Leaderboard" msgstr "Classement" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/dialogs/organisation-leave-dialog.tsx msgid "Leave" msgstr "Quitter" @@ -3541,8 +3634,10 @@ msgstr "Chargement..." msgid "Login" msgstr "Connexion" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/tables/organisation-teams-table.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Manage" msgstr "Gérer" @@ -3555,10 +3650,18 @@ msgstr "Gérer le profil de {0}" msgid "Manage all organisations you are currently associated with." msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/claims.tsx +msgid "Manage all subscription claims" +msgstr "" + #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/templates.$id._index.tsx msgid "Manage and view template" msgstr "Gérer et afficher le modèle" +#: apps/remix/app/components/general/organisations/organisation-billing-portal-button.tsx +msgid "Manage billing" +msgstr "" + #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx msgid "Manage details for this public template" msgstr "Gérer les détails de ce modèle public" @@ -3571,6 +3674,14 @@ msgstr "Gérer le lien direct" msgid "Manage documents" msgstr "Gérer les documents" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Manage organisation" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx +msgid "Manage organisations" +msgstr "" + #: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx msgid "Manage passkeys" msgstr "Gérer les clés d'accès" @@ -3579,17 +3690,20 @@ msgstr "Gérer les clés d'accès" msgid "Manage permissions and access controls" msgstr "" -#: apps/remix/app/components/general/teams/team-billing-portal-button.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "Manage subscription" msgstr "Gérer l'abonnement" -#: apps/remix/app/components/general/billing-portal-button.tsx -msgid "Manage Subscription" +#. placeholder {0}: organisation.name +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Manage the {0} organisation" msgstr "" -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx -msgid "Manage subscriptions" -msgstr "Gérer les abonnements" +#. placeholder {1}: organisation.name +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Manage the {1} organisation subscription" +msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups._index.tsx msgid "Manage the custom groups of members for your organisation." @@ -3661,13 +3775,19 @@ msgstr "Maximum" msgid "Maximum file size: 4MB. Maximum 100 rows per upload. Blank values will use template defaults." msgstr "Taille maximale du fichier : 4 Mo. Maximum de 100 lignes par importation. Les valeurs vides utiliseront les valeurs par défaut du modèle." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/dialogs/team-member-create-dialog.tsx #: packages/lib/constants/teams.ts #: packages/lib/constants/organisations.ts msgid "Member" msgstr "Membre" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Member Count" +msgstr "" + +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/tables/organisation-members-table.tsx msgid "Member Since" msgstr "Membre depuis" @@ -3698,7 +3818,12 @@ msgstr "" msgid "Modify recipients" msgstr "Modifier les destinataires" +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Modify the details of the subscription claim." +msgstr "" + #: apps/remix/app/components/general/billing-plans.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Monthly" msgstr "Mensuel" @@ -3716,9 +3841,11 @@ msgstr "Utilisateurs actifs mensuels : utilisateurs ayant terminé au moins un d #: apps/remix/app/components/tables/admin-leaderboard-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-dashboard-users-table.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx #: apps/remix/app/components/general/claim-account.tsx #: apps/remix/app/components/general/document-signing/document-signing-name-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx @@ -3771,7 +3898,6 @@ msgstr "" msgid "New Template" msgstr "Nouveau modèle" -#: apps/remix/app/components/forms/signup.tsx #: apps/remix/app/components/embed/embed-document-signing-page.tsx #: apps/remix/app/components/embed/embed-direct-template-client-page.tsx #: apps/remix/app/components/dialogs/team-member-create-dialog.tsx @@ -3846,6 +3972,10 @@ msgstr "Aucun résultat trouvé." msgid "No signature field found" msgstr "Aucun champ de signature trouvé" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "No Stripe customer attached" +msgstr "" + #: apps/remix/app/components/tables/team-groups-table.tsx msgid "No team groups found" msgstr "" @@ -3873,6 +4003,7 @@ msgstr "Aucune valeur trouvée." msgid "No worries, it happens! Enter your email and we'll email you a special link to reset your password." msgstr "Pas de soucis, ça arrive ! Entrez votre email et nous vous enverrons un lien spécial pour réinitialiser votre mot de passe." +#: apps/remix/app/components/tables/admin-claims-table.tsx #: packages/lib/constants/document.ts msgid "None" msgstr "Aucun" @@ -3897,6 +4028,16 @@ msgstr "Numéro" msgid "Number format" msgstr "Format de numéro" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Number of members allowed. 0 = Unlimited" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Number of teams allowed. 0 = Unlimited" +msgstr "" + #. placeholder {0}: document.team?.name #: apps/remix/app/components/general/document-signing/document-signing-page-view.tsx msgid "on behalf of \"{0}\" has invited you to approve this document" @@ -3957,10 +4098,6 @@ msgstr "Seules les administrateurs peuvent accéder et voir le document" msgid "Only managers and above can access and view the document" msgstr "Seuls les responsables et au-dessus peuvent accéder et voir le document" -#: apps/remix/app/components/forms/signup.tsx -msgid "Only subscribers can have a username shorter than 6 characters" -msgstr "Seuls les abonnés peuvent avoir un nom d'utilisateur de moins de 6 caractères" - #: apps/remix/app/routes/_profile+/_layout.tsx #: apps/remix/app/components/general/generic-error-layout.tsx #: apps/remix/app/components/general/generic-error-layout.tsx @@ -3980,7 +4117,8 @@ msgstr "Ou" msgid "Or continue with" msgstr "Ou continuez avec" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Organisation" msgstr "" @@ -3996,6 +4134,11 @@ msgstr "" msgid "Organisation Group Settings" msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Organisation has been updated successfully" +msgstr "" + #: apps/remix/app/routes/_unauthenticated+/organisation.invite.$token.tsx msgid "Organisation invitation" msgstr "" @@ -4015,15 +4158,18 @@ msgid "Organisation Member" msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.members.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "Organisation Members" msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/forms/organisation-update-form.tsx #: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Organisation Name" msgstr "" #: apps/remix/app/routes/_authenticated+/_layout.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "Organisation not found" msgstr "" @@ -4036,6 +4182,7 @@ msgid "Organisation role" msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/components/dialogs/organisation-member-invite-dialog.tsx msgid "Organisation Role" msgstr "" @@ -4047,13 +4194,18 @@ msgstr "" msgid "Organisation Settings" msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Organisation Teams" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/forms/organisation-update-form.tsx -#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Organisation URL" msgstr "" #: apps/remix/app/routes/_authenticated+/dashboard.tsx #: apps/remix/app/routes/_authenticated+/settings+/organisations.tsx +#: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx #: apps/remix/app/components/general/settings-nav-mobile.tsx #: apps/remix/app/components/general/settings-nav-desktop.tsx #: apps/remix/app/components/general/menu-switcher.tsx @@ -4076,8 +4228,9 @@ msgstr "Sinon, le document sera créé sous forme de brouillon." #: apps/remix/app/routes/_authenticated+/dashboard.tsx #: apps/remix/app/routes/_authenticated+/dashboard.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/tables/organisation-members-table.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Owner" msgstr "Propriétaire" @@ -4092,10 +4245,6 @@ msgstr "Page {0} sur {1}" msgid "Page {0} of {numPages}" msgstr "Page {0} sur {numPages}" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx -msgid "Paid" -msgstr "Payé" - #: apps/remix/app/components/forms/signin.tsx msgid "Passkey" msgstr "Clé d'accès" @@ -4166,20 +4315,11 @@ msgstr "Mot de passe mis à jour" msgid "Password updated!" msgstr "Mot de passe mis à jour !" -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx -msgid "Pay" -msgstr "Payer" - -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "Payment is required to finalise the creation of your team." -msgstr "Un paiement est requis pour finaliser la création de votre équipe." - -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Payment overdue" msgstr "Paiement en retard" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.teams.tsx #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.members.tsx #: apps/remix/app/components/general/template/template-page-view-documents-table.tsx #: apps/remix/app/components/general/document/document-status.tsx @@ -4205,9 +4345,15 @@ msgstr "Documents en attente" msgid "Pending invitations" msgstr "Invitations en attente" -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx -msgid "Pending team deleted." -msgstr "Équipe en attente supprimée." +#: apps/remix/app/components/general/billing-plans.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "per month" +msgstr "" + +#: apps/remix/app/components/general/billing-plans.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "per year" +msgstr "" #: apps/remix/app/components/general/menu-switcher.tsx msgid "Personal Account" @@ -4369,6 +4515,10 @@ msgstr "Veuiillez taper {0} pour confirmer" msgid "Please type <0>{0} to confirm." msgstr "Veuillez taper <0>{0} pour confirmer." +#: apps/remix/app/components/forms/branding-preferences-form.tsx +msgid "Please upload a logo" +msgstr "" + #: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx msgid "Pre-formatted CSV template with example data." msgstr "Modèle CSV pré-formaté avec des données d'exemple." @@ -4435,10 +4585,6 @@ msgstr "Profil public" msgid "Public profile URL" msgstr "URL du profil public" -#: apps/remix/app/components/forms/signup.tsx -msgid "Public profile username" -msgstr "Nom d'utilisateur du profil public" - #: apps/remix/app/components/tables/templates-table.tsx msgid "Public templates are connected to your public profile. Any modifications to public templates will also appear in your public profile." msgstr "Les modèles publics sont connectés à votre profil public. Toute modification apportée aux modèles publics apparaîtra également dans votre profil public." @@ -4621,7 +4767,6 @@ msgstr "Rappel : Veuillez {recipientActionVerb} ce document" msgid "Reminder: Please {recipientActionVerb} your document" msgstr "Rappel : Veuillez {recipientActionVerb} votre document" -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx #: apps/remix/app/components/tables/team-members-table.tsx #: apps/remix/app/components/tables/team-groups-table.tsx #: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx @@ -4630,7 +4775,7 @@ msgstr "Rappel : Veuillez {recipientActionVerb} votre document" #: apps/remix/app/components/general/teams/team-email-dropdown.tsx #: apps/remix/app/components/general/document-signing/document-signing-field-container.tsx #: apps/remix/app/components/general/document-signing/document-signing-field-container.tsx -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx #: apps/remix/app/components/forms/avatar-image.tsx #: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx #: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx @@ -4710,11 +4855,11 @@ msgstr "Réinitialiser le mot de passe" msgid "Resetting Password..." msgstr "Réinitialisation du mot de passe..." -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Resolve" msgstr "Résoudre" -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Resolve payment" msgstr "Résoudre le paiement" @@ -4754,7 +4899,7 @@ msgstr "Révoquer" msgid "Revoke access" msgstr "Révoquer l'accès" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/tables/team-members-table.tsx #: apps/remix/app/components/tables/team-groups-table.tsx #: apps/remix/app/components/tables/organisation-members-table.tsx @@ -4764,7 +4909,6 @@ msgstr "Révoquer l'accès" #: apps/remix/app/components/dialogs/team-member-update-dialog.tsx #: apps/remix/app/components/dialogs/team-group-update-dialog.tsx #: apps/remix/app/components/dialogs/organisation-member-update-dialog.tsx -#: apps/remix/app/components/dialogs/organisation-member-invite-dialog.tsx msgid "Role" msgstr "Rôle" @@ -4797,6 +4941,14 @@ msgstr "Sauvegarder le modèle" msgid "Search" msgstr "Recherche" +#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx +msgid "Search and manage all organisations" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/claims.tsx +msgid "Search by claim ID or name" +msgstr "" + #: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx msgid "Search by document title" msgstr "Recherche par titre de document" @@ -4806,6 +4958,10 @@ msgstr "Recherche par titre de document" msgid "Search by name or email" msgstr "Recherche par nom ou e-mail" +#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx +msgid "Search by organisation ID, name, customer ID or owner email" +msgstr "" + #: apps/remix/app/components/general/document/document-search.tsx msgid "Search documents..." msgstr "Rechercher des documents..." @@ -4834,6 +4990,14 @@ msgstr "Activité de sécurité" msgid "Select" msgstr "Sélectionner" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Select a plan" +msgstr "" + +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Select a plan to continue" +msgstr "" + #: apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx msgid "Select a team to view its dashboard" msgstr "" @@ -4889,6 +5053,10 @@ msgstr "" msgid "Select passkey" msgstr "Sélectionner la clé d'authentification" +#: apps/remix/app/components/forms/document-preferences-form.tsx +msgid "Select signature types" +msgstr "" + #: apps/remix/app/components/dialogs/organisation-group-create-dialog.tsx msgid "Select the members to add to this group" msgstr "" @@ -5182,10 +5350,6 @@ msgstr "Signature en cours" msgid "Signing Certificate" msgstr "Certificat de signature" -#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx -msgid "Signing certificate provided by" -msgstr "Certificat de signature fourni par" - #: packages/lib/server-only/document/send-completed-email.ts #: packages/lib/server-only/document/send-completed-email.ts msgid "Signing Complete!" @@ -5243,24 +5407,23 @@ msgstr "Certains signataires n'ont pas été assignés à un champ de signature. #: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx #: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.public-profile.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.groups.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.groups.tsx -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/organisation-member-invites-table.tsx #: apps/remix/app/components/tables/organisation-member-invites-table.tsx #: apps/remix/app/components/tables/documents-table-action-dropdown.tsx #: apps/remix/app/components/tables/documents-table-action-button.tsx -#: apps/remix/app/components/general/billing-portal-button.tsx #: apps/remix/app/components/general/billing-plans.tsx -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx #: apps/remix/app/components/general/teams/team-email-usage.tsx #: apps/remix/app/components/general/teams/team-email-dropdown.tsx -#: apps/remix/app/components/general/teams/team-billing-portal-button.tsx #: apps/remix/app/components/general/organisations/organisation-invitations.tsx #: apps/remix/app/components/general/organisations/organisation-invitations.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-portal-button.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx #: apps/remix/app/components/general/document-signing/document-signing-auth-page.tsx #: apps/remix/app/components/general/document-signing/document-signing-auth-account.tsx #: apps/remix/app/components/general/document/document-page-view-dropdown.tsx @@ -5269,7 +5432,6 @@ msgstr "Certains signataires n'ont pas été assignés à un champ de signature. #: apps/remix/app/components/general/document/document-audit-log-download-button.tsx #: apps/remix/app/components/general/direct-template/direct-template-signing-auth-page.tsx #: apps/remix/app/components/general/direct-template/direct-template-page.tsx -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx #: apps/remix/app/components/embed/embed-document-signing-page.tsx #: apps/remix/app/components/embed/embed-direct-template-client-page.tsx #: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx @@ -5279,8 +5441,6 @@ msgstr "Certains signataires n'ont pas été assignés à un champ de signature. #: apps/remix/app/components/dialogs/template-create-dialog.tsx #: apps/remix/app/components/dialogs/team-email-delete-dialog.tsx #: apps/remix/app/components/dialogs/team-email-delete-dialog.tsx -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx #: apps/remix/app/components/dialogs/organisation-member-invite-dialog.tsx #: apps/remix/app/components/dialogs/document-resend-dialog.tsx #: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx @@ -5347,9 +5507,8 @@ msgid "Stats" msgstr "Statistiques" #: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx #: apps/remix/app/components/tables/documents-table.tsx #: apps/remix/app/components/general/template/template-page-view-documents-table.tsx msgid "Status" @@ -5359,6 +5518,18 @@ msgstr "Statut" msgid "Step <0>{step} of {maxStep}" msgstr "Étape <0>{step} sur {maxStep}" +#: apps/remix/app/components/tables/admin-organisations-table.tsx +msgid "Stripe" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Stripe customer created successfully" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Stripe Customer ID" +msgstr "" + #: packages/ui/primitives/template-flow/add-template-settings.tsx #: packages/ui/primitives/document-flow/add-subject.tsx msgid "Subject <0>(Optional)" @@ -5368,17 +5539,32 @@ msgstr "Objet <0>(Optionnel)" msgid "Subscribe" msgstr "" -#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Subscription" msgstr "Abonnement" -#: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx -msgid "Subscriptions" -msgstr "Abonnements" +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Subscription claim created successfully." +msgstr "" + +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "Subscription claim deleted successfully." +msgstr "" + +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Subscription claim updated successfully." +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/claims.tsx +msgid "Subscription Claims" +msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/organisation-member-invites-table.tsx @@ -5440,8 +5626,8 @@ msgid "System Theme" msgstr "Thème système" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/tables/organisation-teams-table.tsx -#: apps/remix/app/components/tables/organisation-pending-teams-table.tsx msgid "Team" msgstr "Équipe" @@ -5458,9 +5644,10 @@ msgstr "" msgid "Team Assignments" msgstr "" -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "Team checkout" -msgstr "Vérification de l'équipe" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Team Count" +msgstr "" #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx @@ -5570,6 +5757,10 @@ msgstr "Paramètres de l'équipe" msgid "Team templates" msgstr "Modèles d'équipe" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Team url" +msgstr "" + #: apps/remix/app/components/forms/team-update-form.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx msgid "Team URL" @@ -5587,7 +5778,7 @@ msgstr "Équipes" msgid "Teams help you organize your work and collaborate with others. Create your first team to get started." msgstr "" -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Teams restricted" msgstr "Équipes restreintes" @@ -5764,6 +5955,12 @@ msgstr "" msgid "The organisation role that will be applied to all members in this group." msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "" +"The organisation you are looking for may have been removed, renamed or may have never\n" +" existed." +msgstr "" + #: apps/remix/app/routes/_authenticated+/_layout.tsx msgid "" "The organisation you are looking for may have been removed, renamed or may have never\n" @@ -5936,6 +6133,10 @@ msgstr "Cette action est réversible, mais veuillez faire attention car le compt msgid "This can be overriden by setting the authentication requirements directly on each recipient in the next step." msgstr "Cela peut être remplacé par le paramétrage direct des exigences d'authentification pour chaque destinataire à l'étape suivante." +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "This claim is locked and cannot be deleted." +msgstr "" + #: packages/email/template-components/template-document-super-delete.tsx msgid "This document can not be recovered, if you would like to dispute the reason for future documents please contact support." msgstr "Ce document ne peut pas être récupéré, si vous souhaitez contester la raison des documents futurs, veuillez contacter le support." @@ -6020,6 +6221,10 @@ msgstr "Ce champ ne peut pas être modifié ou supprimé. Lorsque vous partagez msgid "This is how the document will reach the recipients once the document is ready for signing." msgstr "Voici comment le document atteindra les destinataires une fois qu'il sera prêt à être signé." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "This is the claim that this organisation was initially created with. Any feature flag changes to this claim will be backported into this organisation." +msgstr "" + #: apps/remix/app/routes/_unauthenticated+/team.verify.transfer.token.tsx msgid "This link is invalid or has expired." msgstr "" @@ -6040,10 +6245,6 @@ msgstr "Cette clé d'accès a déjà été enregistrée." msgid "This passkey is not configured for this application. Please login and add one in the user settings." msgstr "Cette clé d'accès n'est pas configurée pour cette application. Veuillez vous connecter et en ajouter une dans les paramètres de l'utilisateur." -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "This price includes minimum 5 seats." -msgstr "Ce prix inclut un minimum de 5 sièges." - #: packages/ui/primitives/document-flow/add-fields.tsx msgid "This recipient can no longer be modified as they have signed a field, or completed the document." msgstr "Ce destinataire ne peut plus être modifié car il a signé un champ ou complété le document." @@ -6075,14 +6276,9 @@ msgstr "Ce token est invalide ou a expiré. Veuillez contacter votre équipe pou #: apps/remix/app/components/forms/team-update-form.tsx #: apps/remix/app/components/forms/organisation-update-form.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx -#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "This URL is already in use." msgstr "Cette URL est déjà utilisée." -#: apps/remix/app/components/forms/signup.tsx -msgid "This username has already been taken" -msgstr "Ce nom d'utilisateur a déjà été pris" - #: packages/ui/components/document/document-email-checkboxes.tsx msgid "This will be sent to all recipients if a pending document has been deleted." msgstr "Cela sera envoyé à tous les destinataires si un document en attente a été supprimé." @@ -6095,6 +6291,10 @@ msgstr "Cela sera envoyé à tous les destinataires une fois que le document aur msgid "This will be sent to the document owner once the document has been fully completed." msgstr "Cela sera envoyé au propriétaire du document une fois que le document aura été entièrement complété." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "This will ONLY backport feature flags which are set to true, anything disabled in the initial claim will not be backported" +msgstr "" + #: packages/ui/components/recipient/recipient-action-auth-select.tsx msgid "This will override any global settings." msgstr "Cela remplacera tous les paramètres globaux." @@ -6385,20 +6585,27 @@ msgstr "Non complet" msgid "Unknown" msgstr "Inconnu" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx -msgid "Unpaid" -msgstr "Non payé" +#: apps/remix/app/components/tables/admin-claims-table.tsx +msgid "Unlimited" +msgstr "" + +#: packages/lib/types/subscription.ts +msgid "Unlimited documents, API and more" +msgstr "" #: apps/remix/app/components/dialogs/team-group-create-dialog.tsx msgid "Untitled Group" msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx #: apps/remix/app/components/forms/public-profile-form.tsx #: apps/remix/app/components/forms/document-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx #: apps/remix/app/components/dialogs/team-member-update-dialog.tsx #: apps/remix/app/components/dialogs/team-group-update-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx @@ -6413,6 +6620,14 @@ msgstr "Mettre à jour" msgid "Update Banner" msgstr "Mettre à jour la bannière" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx +msgid "Update Billing" +msgstr "" + +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Update Claim" +msgstr "" + #: apps/remix/app/components/forms/organisation-update-form.tsx msgid "Update organisation" msgstr "" @@ -6445,6 +6660,10 @@ msgstr "Mettre à jour le destinataire" msgid "Update role" msgstr "Mettre à jour le rôle" +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Update Subscription Claim" +msgstr "" + #: apps/remix/app/components/forms/team-update-form.tsx msgid "Update team" msgstr "Mettre à jour l'équipe" @@ -6524,7 +6743,7 @@ msgstr "Importer une signature" msgid "Upload Template Document" msgstr "Importer le document modèle" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Upload your brand logo (max 5MB, JPG, PNG, or WebP)" msgstr "Téléchargez votre logo de marque (max 5 Mo, JPG, PNG ou WebP)" @@ -6572,10 +6791,6 @@ msgstr "Utilisateur" msgid "User has no password." msgstr "L'utilisateur n'a pas de mot de passe." -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx -msgid "User ID" -msgstr "ID utilisateur" - #: apps/remix/app/components/dialogs/admin-user-enable-dialog.tsx #: apps/remix/app/components/dialogs/admin-user-disable-dialog.tsx #: apps/remix/app/components/dialogs/admin-user-delete-dialog.tsx @@ -6590,10 +6805,6 @@ msgstr "Les profils des utilisateurs sont ici !" msgid "User with this email already exists. Please use a different email address." msgstr "Un utilisateur avec cet e-mail existe déjà. Veuillez utiliser une adresse e-mail différente." -#: apps/remix/app/components/forms/signup.tsx -msgid "Username can only container alphanumeric characters and dashes." -msgstr "Le nom d'utilisateur ne peut contenir que des caractères alphanumériques et des tirets." - #: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx msgid "Users" msgstr "Utilisateurs" @@ -6641,7 +6852,7 @@ msgstr "Vérifiez votre adresse e-mail d'équipe" msgid "Version History" msgstr "Historique des versions" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx #: apps/remix/app/components/tables/inbox-table.tsx #: apps/remix/app/components/tables/documents-table-action-dropdown.tsx #: apps/remix/app/components/tables/documents-table-action-button.tsx @@ -6709,6 +6920,10 @@ msgstr "Voir plus" msgid "View Original Document" msgstr "Voir le document original" +#: apps/remix/app/components/tables/admin-organisations-table.tsx +msgid "View owner" +msgstr "" + #: packages/email/template-components/template-document-self-signed.tsx msgid "View plans" msgstr "Voir les forfaits" @@ -6778,9 +6993,8 @@ msgstr "Vous voulez votre propre profil public ?" msgid "Warning: Assistant as last signer" msgstr "" -#: apps/remix/app/components/general/billing-portal-button.tsx -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx -#: apps/remix/app/components/general/teams/team-billing-portal-button.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-portal-button.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "We are unable to proceed to the billing portal at this time. Please try again, or contact support." msgstr "Nous ne pouvons pas accéder au portail de facturation pour le moment. Veuillez réessayer ou contacter le support." @@ -6792,10 +7006,19 @@ msgstr "Nous ne pouvons pas supprimer cette clé de passkey pour le moment. Veui msgid "We are unable to update this passkey at the moment. Please try again later." msgstr "Nous ne pouvons pas mettre à jour cette clé de passkey pour le moment. Veuillez réessayer plus tard." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "We couldn't create a Stripe customer. Please try again." +msgstr "" + #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx msgid "We couldn't update the group. Please try again." msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "We couldn't update the organisation. Please try again." +msgstr "" + #: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx msgid "We encountered an error while removing the direct template link. Please try again later." msgstr "Une erreur s'est produite lors de la suppression du lien direct vers le modèle. Veuillez réessayer plus tard." @@ -6829,10 +7052,6 @@ msgstr "Une erreur inconnue s'est produite lors de la création d'une équipe. V msgid "We encountered an unknown error while attempting to delete it. Please try again later." msgstr "Une erreur inconnue s'est produite lors de la suppression. Veuillez réessayer plus tard." -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx -msgid "We encountered an unknown error while attempting to delete the pending team. Please try again later." -msgstr "Une erreur inconnue s'est produite lors de la suppression de l'équipe en attente. Veuillez réessayer plus tard." - #: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx msgid "We encountered an unknown error while attempting to delete this organisation. Please try again later." msgstr "" @@ -6943,10 +7162,6 @@ msgstr "Nous avons rencontré une erreur inconnue lors de la tentative de mise msgid "We have sent a confirmation email for verification." msgstr "Nous avons envoyé un e-mail de confirmation pour vérification." -#: apps/remix/app/components/forms/signup.tsx -msgid "We need a username to create your profile" -msgstr "Nous avons besoin d'un nom d'utilisateur pour créer votre profil" - #: apps/remix/app/components/forms/signup.tsx msgid "We need your signature to sign documents" msgstr "Nous avons besoin de votre signature pour signer des documents" @@ -6959,10 +7174,6 @@ msgstr "Nous n'avons pas pu copier le token dans votre presse-papiers. Veuillez msgid "We were unable to copy your recovery code to your clipboard. Please try again." msgstr "Nous n'avons pas pu copier votre code de récupération dans votre presse-papiers. Veuillez réessayer." -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "We were unable to create a checkout session. Please try again, or contact support" -msgstr "Nous n'avons pas pu créer de session de paiement. Veuillez réessayer ou contacter le support" - #: apps/remix/app/components/forms/signup.tsx msgid "We were unable to create your account. Please review the information you provided and try again." msgstr "Nous n'avons pas pu créer votre compte. Veuillez vérifier les informations que vous avez fournies et réessayer." @@ -6992,7 +7203,7 @@ msgstr "Nous n'avons pas pu configurer l'authentification à deux facteurs pour msgid "We were unable to submit this document at this time. Please try again later." msgstr "Nous n'avons pas pu soumettre ce document pour le moment. Veuillez réessayer plus tard." -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx msgid "We were unable to update your branding preferences at this time, please try again later" msgstr "Nous n'avons pas pu mettre à jour vos préférences de branding pour le moment, veuillez réessayer plus tard" @@ -7065,10 +7276,6 @@ msgstr "URL du webhook" msgid "Webhooks" msgstr "Webhooks" -#: apps/remix/app/components/general/billing-plans.tsx -msgid "Weekly" -msgstr "" - #: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx msgid "Welcome" msgstr "Bienvenue" @@ -7114,6 +7321,10 @@ msgstr "Lorsque vous utilisez notre plateforme pour apposer votre signature éle msgid "While waiting for them to do so you can create your own Documenso account and get started with document signing right away." msgstr "En attendant qu'ils le fassent, vous pouvez créer votre propre compte Documenso et commencer à signer des documents dès maintenant." +#: packages/lib/types/subscription.ts +msgid "Whitelabeling, unlimited members and more" +msgstr "" + #: apps/remix/app/components/dialogs/document-resend-dialog.tsx msgid "Who do you want to remind?" msgstr "Qui voulez-vous rappeler ?" @@ -7131,6 +7342,7 @@ msgid "Write about yourself" msgstr "Écrivez sur vous-même" #: apps/remix/app/components/general/billing-plans.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Yearly" msgstr "Annuel" @@ -7200,6 +7412,10 @@ msgstr "Vous êtes sur le point de révoquer l'accès de l'équipe <0>{0} ({ msgid "You are about to send this document to the recipients. Are you sure you want to continue?" msgstr "Vous êtes sur le point d'envoyer ce document aux destinataires. Êtes-vous sûr de vouloir continuer ?" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "You are currently on the <0>Free Plan." +msgstr "" + #: apps/remix/app/components/dialogs/team-member-update-dialog.tsx msgid "You are currently updating <0>{memberName}." msgstr "" @@ -7306,12 +7522,12 @@ msgstr "Vous ne pouvez pas importer de documents pour le moment." msgid "You cannot upload encrypted PDFs" msgstr "Vous ne pouvez pas importer de PDF cryptés" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx -msgid "You currently have no access to any teams within this organisation. Please contact your organisation to request access." +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "You currently have an inactive <0>{currentProductName} subscription" msgstr "" -#: apps/remix/app/components/general/billing-portal-button.tsx -msgid "You do not currently have a customer record, this should not happen. Please contact support for assistance." +#: apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx +msgid "You currently have no access to any teams within this organisation. Please contact your organisation to request access." msgstr "" #: apps/remix/app/components/forms/token.tsx @@ -7346,10 +7562,6 @@ msgstr "Vous avez été invité à rejoindre {0} sur Documenso" msgid "You have been invited to join the following organisation" msgstr "" -#: packages/email/templates/organisation-invite.tsx -msgid "You have been invited to join the following team" -msgstr "Vous avez été invité à rejoindre l'équipe suivante" - #: packages/lib/server-only/recipient/set-document-recipients.ts #: packages/lib/server-only/recipient/delete-document-recipient.ts msgid "You have been removed from a document" @@ -7383,6 +7595,10 @@ msgstr "Vous n'avez pas encore créé ou reçu de documents. Pour créer un docu msgid "You have reached the maximum limit of {0} direct templates. <0>Upgrade your account to continue!" msgstr "Vous avez atteint la limite maximale de {0} modèles directs. <0>Mettez à niveau votre compte pour continuer !" +#: apps/remix/app/components/dialogs/team-create-dialog.tsx +msgid "You have reached the maximum number of teams for your plan. Please contact sales at <0>support@documenso.com if you would like to adjust your plan." +msgstr "" + #: apps/remix/app/components/general/document/document-upload.tsx msgid "You have reached your document limit for this month. Please upgrade your plan." msgstr "Vous avez atteint votre limite de documents pour ce mois. Veuillez passer à l'abonnement supérieur." @@ -7482,10 +7698,6 @@ msgstr "Vous devez être connecté pour voir cette page." msgid "You need to setup 2FA to mark this document as viewed." msgstr "Vous devez configurer 2FA pour marquer ce document comme vu." -#: apps/remix/app/components/forms/signup.tsx -msgid "You will get notified & be able to set up your documenso public profile when we launch the feature." -msgstr "Vous serez notifié et pourrez configurer votre profil public Documenso lorsque nous lancerons la fonctionnalité." - #: apps/remix/app/components/forms/2fa/enable-authenticator-app-dialog.tsx msgid "You will now be required to enter a code from your authenticator app when signing in." msgstr "Vous devrez maintenant entrer un code de votre application d'authentification lors de la connexion." @@ -7506,11 +7718,11 @@ msgstr "Votre avatar a été mis à jour avec succès." msgid "Your banner has been updated successfully." msgstr "Votre bannière a été mise à jour avec succès." -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Your brand website URL" msgstr "L'URL de votre site web de marque" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx msgid "Your branding preferences have been updated" msgstr "Vos préférences de branding ont été mises à jour" @@ -7522,6 +7734,18 @@ msgstr "Votre envoi groupé a été initié. Vous recevrez une notification par msgid "Your bulk send operation for template \"{templateName}\" has completed." msgstr "Votre envoi groupé pour le modèle \"{templateName}\" est terminé." +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "Your current {currentProductName} plan is past due. Please update your payment information." +msgstr "" + +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "Your current plan is inactive." +msgstr "" + +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "Your current plan is past due." +msgstr "" + #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx msgid "Your direct signing templates" msgstr "Vos modèles de signature directe" @@ -7611,7 +7835,7 @@ msgstr "Votre mot de passe a été mis à jour avec succès." msgid "Your password has been updated." msgstr "Votre mot de passe a été mis à jour." -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Your payment for teams is overdue. Please settle the payment to avoid any service disruptions." msgstr "Votre paiement pour les équipes est en retard. Veuillez régler le paiement pour éviter toute interruption de service." diff --git a/packages/lib/translations/it/web.po b/packages/lib/translations/it/web.po index 228fcd0ef..e31aea462 100644 --- a/packages/lib/translations/it/web.po +++ b/packages/lib/translations/it/web.po @@ -73,11 +73,6 @@ msgstr "{0, plural, one {# carattere oltre il limite} other {# caratteri oltre i msgid "{0, plural, one {# recipient} other {# recipients}}" msgstr "{0, plural, one {# destinatario} other {# destinatari}}" -#. placeholder {0}: row.original.quantity -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx -msgid "{0, plural, one {# Seat} other {# Seats}}" -msgstr "{0, plural, one {# posto} other {# posti}}" - #. placeholder {0}: org.teams.length #: apps/remix/app/routes/_authenticated+/dashboard.tsx msgid "{0, plural, one {# team} other {# teams}}" @@ -132,16 +127,6 @@ msgstr "{0} ti ha invitato a {recipientActionVerb} il documento \"{1}\"." msgid "{0} invited you to {recipientActionVerb} a document" msgstr "{0} ti ha invitato a {recipientActionVerb} un documento" -#. placeholder {0}: memberName || memberEmail -#: packages/email/templates/team-join.tsx -msgid "{0} joined the team {teamName} on Documenso" -msgstr "{0} si è unito al team {teamName} su Documenso" - -#. placeholder {0}: memberName || memberEmail -#: packages/email/templates/team-leave.tsx -msgid "{0} left the team {teamName} on Documenso" -msgstr "{0} ha lasciato il team {teamName} su Documenso" - #. placeholder {0}: remaining.documents #. placeholder {1}: quota.documents #: apps/remix/app/components/general/document/document-upload.tsx @@ -223,14 +208,6 @@ msgstr "{inviterName} per conto di \"{teamName}\" ti ha invitato a {0}<0/>\"{doc msgid "{inviterName} on behalf of \"{teamName}\" has invited you to {action} {documentName}" msgstr "{inviterName} per conto di \"{teamName}\" ti ha invitato a {action} {documentName}" -#: packages/email/templates/team-join.tsx -msgid "{memberEmail} joined the following team" -msgstr "{memberEmail} si è unito al seguente team" - -#: packages/email/templates/team-leave.tsx -msgid "{memberEmail} left the following team" -msgstr "{memberEmail} ha lasciato il seguente team" - #: packages/lib/utils/document-audit-logs.ts msgid "{prefix} added a field" msgstr "{prefix} ha aggiunto un campo" @@ -484,6 +461,14 @@ msgstr "<0>Stai per completare la firma di \"<1>{documentTitle}\".<2/> S msgid "<0>You are about to complete viewing \"<1>{documentTitle}\".<2/> Are you sure?" msgstr "<0>Stai per completare la visualizzazione di \"<1>{documentTitle}\".<2/> Sei sicuro?" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "0 Free organisations left" +msgstr "" + +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "1 Free organisations left" +msgstr "" + #: apps/remix/app/components/forms/token.tsx msgid "1 month" msgstr "{VAR_PLURAL, select, one {1 mese} other {# mesi}}" @@ -510,6 +495,7 @@ msgid "404 Organisation group not found" msgstr "" #: apps/remix/app/routes/_authenticated+/_layout.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "404 Organisation not found" msgstr "" @@ -522,6 +508,14 @@ msgstr "404 Profilo non trovato" msgid "404 Team not found" msgstr "404 Squadra non trovata" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "5 documents a month" +msgstr "" + +#: packages/lib/types/subscription.ts +msgid "5 Documents a month" +msgstr "" + #: apps/remix/app/components/general/generic-error-layout.tsx msgid "500 Internal Server Error" msgstr "" @@ -566,9 +560,29 @@ msgstr "Un campo è stato aggiornato" msgid "A means to print or download documents for your records" msgstr "Un mezzo per stampare o scaricare documenti per i tuoi archivi" -#: packages/lib/jobs/definitions/emails/send-team-member-joined-email.handler.ts -msgid "A new member has joined your team" -msgstr "Un nuovo membro si è unito al tuo team" +#: packages/email/templates/organisation-join.tsx +msgid "A member has joined your organisation on Documenso" +msgstr "" + +#: packages/lib/jobs/definitions/emails/send-organisation-member-left-email.handler.ts +msgid "A member has left your organisation" +msgstr "" + +#: packages/email/templates/organisation-leave.tsx +msgid "A member has left your organisation {organisationName}" +msgstr "" + +#: packages/email/templates/organisation-leave.tsx +msgid "A member has left your organisation on Documenso" +msgstr "" + +#: packages/lib/jobs/definitions/emails/send-organisation-member-joined-email.handler.ts +msgid "A new member has joined your organisation" +msgstr "" + +#: packages/email/templates/organisation-join.tsx +msgid "A new member has joined your organisation {organisationName}" +msgstr "" #: apps/remix/app/components/forms/token.tsx msgid "A new token was created successfully." @@ -608,19 +622,6 @@ msgstr "Un codice segreto che verrà inviato al tuo URL in modo che tu possa ver msgid "A stable internet connection" msgstr "Una connessione Internet stabile" -#: packages/email/templates/team-join.tsx -msgid "A team member has joined a team on Documenso" -msgstr "Un membro del team si è unito a un team su Documenso" - -#. placeholder {0}: team.name -#: packages/lib/jobs/definitions/emails/send-team-member-left-email.handler.ts -msgid "A team member has left {0}" -msgstr "Un membro del team ha lasciato {0}" - -#: packages/email/templates/team-leave.tsx -msgid "A team member has left a team on Documenso" -msgstr "Un membro del team ha lasciato un team su Documenso" - #: packages/email/templates/team-delete.tsx #: packages/email/templates/team-delete.tsx msgid "A team you were a part of has been deleted" @@ -630,8 +631,11 @@ msgstr "Un team di cui facevi parte è stato eliminato" msgid "A unique URL to access your profile" msgstr "Un URL univoco per accedere al tuo profilo" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "A unique URL to identify the organisation" +msgstr "" + #: apps/remix/app/components/forms/organisation-update-form.tsx -#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "A unique URL to identify your organisation" msgstr "" @@ -651,8 +655,8 @@ msgid "Accept" msgstr "Accetta" #: packages/email/templates/organisation-invite.tsx -msgid "Accept invitation to join a team on Documenso" -msgstr "Accetta l'invito a unirti a un team su Documenso" +msgid "Accept invitation to join an organisation on Documenso" +msgstr "" #: packages/email/templates/confirm-team-email.tsx msgid "Accept team email request for {teamName} on Documenso" @@ -720,11 +724,12 @@ msgstr "Azione" #: apps/remix/app/components/tables/organisation-groups-table.tsx #: apps/remix/app/components/tables/inbox-table.tsx #: apps/remix/app/components/tables/documents-table.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx #: apps/remix/app/components/general/template/template-page-view-documents-table.tsx msgid "Actions" msgstr "Azioni" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.teams.tsx #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.members.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx msgid "Active" @@ -865,11 +870,7 @@ msgstr "Aggiungi le persone che firmeranno il documento." msgid "Add the recipients to create the document with" msgstr "Aggiungi i destinatari con cui creare il documento" -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "Adding and removing seats will adjust your invoice accordingly." -msgstr "Aggiungere e rimuovere posti adeguerà di conseguenza la tua fattura." - -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Additional brand information to display at the bottom of emails" msgstr "Informazioni aggiuntive sul marchio da mostrare in fondo alle email" @@ -886,6 +887,10 @@ msgstr "Azioni amministrative" msgid "Admin panel" msgstr "Pannello admin" +#: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx +msgid "Admin Panel" +msgstr "" + #: packages/ui/primitives/template-flow/add-template-settings.tsx #: packages/ui/primitives/document-flow/add-settings.tsx msgid "Advanced Options" @@ -962,6 +967,10 @@ msgstr "" msgid "Allowed Signature Types" msgstr "" +#: apps/remix/app/components/tables/admin-claims-table.tsx +msgid "Allowed teams" +msgstr "" + #: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx msgid "Allows authenticating using biometrics, password managers, hardware keys, etc." msgstr "Consente di autenticare utilizzando biometria, gestori di password, chiavi hardware, ecc." @@ -970,7 +979,7 @@ msgstr "Consente di autenticare utilizzando biometria, gestori di password, chia msgid "Already have an account? <0>Sign in instead" msgstr "Hai già un account? <0>Accedi al posto" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx msgid "Amount" msgstr "Importo" @@ -991,6 +1000,8 @@ msgid "An email containing an invitation will be sent to each member." msgstr "Verrà inviato un'email contenente un invito a ciascun membro." #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/general/claim-account.tsx #: apps/remix/app/components/forms/token.tsx #: apps/remix/app/components/forms/signup.tsx @@ -1230,6 +1241,10 @@ msgstr "Approvazione in corso" msgid "Are you sure you want to complete the document? This action cannot be undone. Please ensure that you have completed prefilling all relevant fields before proceeding." msgstr "" +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "Are you sure you want to delete the following claim?" +msgstr "" + #: apps/remix/app/components/dialogs/token-delete-dialog.tsx msgid "Are you sure you want to delete this token?" msgstr "Sei sicuro di voler eliminare questo token?" @@ -1340,9 +1355,9 @@ msgid "Awaiting email confirmation" msgstr "In attesa della conferma email" #: apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx -#: apps/remix/app/components/forms/signup.tsx #: apps/remix/app/components/dialogs/team-member-create-dialog.tsx #: apps/remix/app/components/dialogs/team-group-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Back" msgstr "Indietro" @@ -1367,29 +1382,17 @@ msgstr "Codici di Backup" msgid "Banner Updated" msgstr "Banner Aggiornato" -#: apps/remix/app/components/forms/signup.tsx -msgid "Basic details" -msgstr "Dettagli di Base" - #: packages/email/template-components/template-confirmation-email.tsx msgid "Before you get started, please confirm your email address by clicking the button below:" msgstr "Prima di iniziare, conferma il tuo indirizzo email facendo clic sul pulsante qui sotto:" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings._layout.tsx -#: apps/remix/app/routes/_authenticated+/settings+/billing.tsx -#: apps/remix/app/components/general/settings-nav-mobile.tsx -#: apps/remix/app/components/general/settings-nav-desktop.tsx #: apps/remix/app/components/general/teams/team-settings-nav-mobile.tsx #: apps/remix/app/components/general/teams/team-settings-nav-desktop.tsx msgid "Billing" msgstr "Fatturazione" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx -#: apps/remix/app/routes/_authenticated+/settings+/billing.tsx -msgid "Billing has been moved to organisations" -msgstr "" - #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx msgid "Black" msgstr "Nero" @@ -1398,12 +1401,13 @@ msgstr "Nero" msgid "Blue" msgstr "Blu" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx msgid "Branding Preferences" msgstr "Preferenze per il branding" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx msgid "Branding preferences updated" msgstr "Preferenze di branding aggiornate" @@ -1512,7 +1516,7 @@ msgstr "" #: apps/remix/app/components/dialogs/team-email-add-dialog.tsx #: apps/remix/app/components/dialogs/team-delete-dialog.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx +#: apps/remix/app/components/dialogs/team-create-dialog.tsx #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx #: apps/remix/app/components/dialogs/organisation-member-update-dialog.tsx @@ -1523,9 +1527,13 @@ msgstr "" #: apps/remix/app/components/dialogs/organisation-group-create-dialog.tsx #: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx #: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx #: apps/remix/app/components/dialogs/document-resend-dialog.tsx #: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx #: apps/remix/app/components/dialogs/document-delete-dialog.tsx +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx #: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx #: packages/ui/primitives/signature-pad/signature-pad-dialog.tsx #: packages/ui/primitives/document-flow/send-document-action-dialog.tsx @@ -1574,7 +1582,7 @@ msgstr "Casella di controllo" msgid "Checkbox values" msgstr "Valori della casella di controllo" -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Checkout" msgstr "Pagamento" @@ -1598,13 +1606,9 @@ msgstr "Scegli..." msgid "Claim account" msgstr "Rivendica account" -#: apps/remix/app/components/forms/signup.tsx -msgid "Claim username" -msgstr "Rivendica nome utente" - -#: apps/remix/app/components/forms/signup.tsx -msgid "Claim your username now" -msgstr "Rivendica il tuo nome utente ora" +#: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx +msgid "Claims" +msgstr "" #: apps/remix/app/components/dialogs/template-use-dialog.tsx msgid "Clear file" @@ -1660,6 +1664,10 @@ msgstr "Clicca per inserire il campo" msgid "Close" msgstr "Chiudi" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Compare all plans and features in detail" +msgstr "" + #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx #: apps/remix/app/components/forms/signup.tsx @@ -1782,6 +1790,10 @@ msgstr "Consenso alle transazioni elettroniche" msgid "Contact Information" msgstr "Informazioni di contatto" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Contact sales here" +msgstr "" + #: apps/remix/app/routes/_authenticated+/admin+/site-settings.tsx msgid "Content" msgstr "Contenuto" @@ -1793,6 +1805,7 @@ msgstr "Contenuto" #: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx #: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx #: packages/ui/primitives/document-flow/document-flow-root.tsx msgid "Continue" @@ -1892,6 +1905,7 @@ msgstr "Copia il token" #: apps/remix/app/routes/_profile+/_layout.tsx #: apps/remix/app/components/dialogs/webhook-create-dialog.tsx #: apps/remix/app/components/dialogs/organisation-group-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Create" msgstr "Crea" @@ -1928,6 +1942,14 @@ msgstr "Crea come bozza" msgid "Create as pending" msgstr "Crea come in attesa" +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Create claim" +msgstr "" + +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Create Claim" +msgstr "" + #: apps/remix/app/components/dialogs/template-direct-link-dialog-wrapper.tsx msgid "Create Direct Link" msgstr "Crea Link Diretto" @@ -1962,18 +1984,26 @@ msgstr "Crea uno automaticamente" msgid "Create organisation" msgstr "" -#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx -msgid "Create Organisation" -msgstr "" - #: apps/remix/app/components/general/menu-switcher.tsx -msgid "Create Organization" +msgid "Create Organisation" msgstr "" #: apps/remix/app/components/dialogs/template-use-dialog.tsx msgid "Create signing links" msgstr "Crea link di firma" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Create Stripe customer" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Create subscription" +msgstr "" + +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Create Subscription Claim" +msgstr "" + #: apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx @@ -2025,7 +2055,7 @@ msgid "Created" msgstr "Creato" #: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Created At" msgstr "Creato il" @@ -2034,7 +2064,6 @@ msgid "Created by" msgstr "Creato da" #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx -#: apps/remix/app/components/tables/organisation-pending-teams-table.tsx msgid "Created on" msgstr "Creato il" @@ -2065,12 +2094,16 @@ msgstr "Destinatari attuali:" msgid "Currently all organisation members can access this team" msgstr "" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups._index.tsx -msgid "Custom Organisation Groups" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx +msgid "Currently branding can only be configured for Teams and above plans." msgstr "" -#: apps/remix/app/components/general/billing-plans.tsx -msgid "Daily" +#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx +msgid "Currently branding can only be configured on the organisation level." +msgstr "" + +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups._index.tsx +msgid "Custom Organisation Groups" msgstr "" #: apps/remix/app/components/general/app-command-menu.tsx @@ -2130,16 +2163,20 @@ msgstr "elimina" #: apps/remix/app/components/tables/organisation-teams-table.tsx #: apps/remix/app/components/tables/organisation-groups-table.tsx #: apps/remix/app/components/tables/documents-table-action-dropdown.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx #: apps/remix/app/components/general/document/document-page-view-dropdown.tsx #: apps/remix/app/components/dialogs/webhook-delete-dialog.tsx #: apps/remix/app/components/dialogs/token-delete-dialog.tsx #: apps/remix/app/components/dialogs/template-delete-dialog.tsx #: apps/remix/app/components/dialogs/team-group-delete-dialog.tsx #: apps/remix/app/components/dialogs/team-delete-dialog.tsx +#: apps/remix/app/components/dialogs/team-delete-dialog.tsx #: apps/remix/app/components/dialogs/organisation-member-delete-dialog.tsx #: apps/remix/app/components/dialogs/organisation-group-delete-dialog.tsx #: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx #: apps/remix/app/components/dialogs/document-delete-dialog.tsx +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx msgid "Delete" msgstr "Elimina" @@ -2179,7 +2216,6 @@ msgid "Delete Document" msgstr "Elimina Documento" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.general.tsx -#: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx msgid "Delete organisation" msgstr "" @@ -2195,8 +2231,11 @@ msgstr "" msgid "Delete passkey" msgstr "Elimina chiave d'accesso" +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "Delete Subscription Claim" +msgstr "" + #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx -#: apps/remix/app/components/dialogs/team-delete-dialog.tsx msgid "Delete team" msgstr "Elimina squadra" @@ -2620,7 +2659,7 @@ msgstr "Documenti visualizzati" msgid "Don't have an account? <0>Sign up" msgstr "Non hai un account? <0>Registrati" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx #: apps/remix/app/components/tables/documents-table-action-dropdown.tsx #: apps/remix/app/components/tables/documents-table-action-button.tsx #: apps/remix/app/components/general/document/document-page-view-dropdown.tsx @@ -2675,7 +2714,7 @@ msgstr "Menu a tendina" msgid "Dropdown options" msgstr "Opzioni del menu a tendina" -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Due to an unpaid invoice, your team has been restricted. Please settle the payment to restore full access to your team." msgstr "A causa di una fattura non pagata, il vostro team è stato limitato. Si prega di effettuare il pagamento per ripristinare l'accesso completo al team." @@ -2718,6 +2757,7 @@ msgstr "Divulgazione della firma elettronica" #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-dashboard-users-table.tsx #: apps/remix/app/components/general/document-signing/document-signing-email-field.tsx @@ -2790,6 +2830,10 @@ msgstr "Verifica email rimossa" msgid "Email verification has been resent" msgstr "Verifica email rinviata" +#: packages/lib/types/subscription.ts +msgid "Embedding, 5 members included and more" +msgstr "" + #: packages/ui/primitives/document-flow/add-fields.tsx msgid "Empty field" msgstr "Campo vuoto" @@ -2820,7 +2864,7 @@ msgstr "Abilita Account" msgid "Enable Authenticator App" msgstr "Abilita l'app Authenticator" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Enable custom branding for all documents in this team." msgstr "Abilita il branding personalizzato per tutti i documenti in questo team." @@ -2849,11 +2893,11 @@ msgstr "Abilitare l'account consente all'utente di utilizzare nuovamente l'accou msgid "Enclosed Document" msgstr "Documento Allegato" -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx -msgid "Ends On" -msgstr "Termina il" +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Enter claim name" +msgstr "" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Enter your brand details" msgstr "Inserisci i dettagli del tuo marchio" @@ -2873,7 +2917,12 @@ msgstr "Inserisci il tuo nome" msgid "Enter your text here" msgstr "Inserisci il tuo testo qui" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Enterprise" +msgstr "" + #: apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx #: apps/remix/app/components/general/verify-email-banner.tsx #: apps/remix/app/components/general/template/template-edit-form.tsx @@ -2948,6 +2997,14 @@ msgstr "Scade il {0}" msgid "External ID" msgstr "ID esterno" +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Failed to create subscription claim." +msgstr "" + +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "Failed to delete subscription claim." +msgstr "" + #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx msgid "Failed to reseal document" msgstr "Fallito il risigillo del documento" @@ -2960,6 +3017,10 @@ msgstr "Impossibile salvare le impostazioni." msgid "Failed to update recipient" msgstr "Aggiornamento destinario fallito" +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Failed to update subscription claim." +msgstr "" + #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx msgid "Failed to update webhook" msgstr "Aggiornamento webhook fallito" @@ -2968,6 +3029,12 @@ msgstr "Aggiornamento webhook fallito" msgid "Failed: {failedCount}" msgstr "Falliti: {failedCount}" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Feature Flags" +msgstr "" + #: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx msgid "Field character limit" msgstr "Limite di caratteri del campo" @@ -3019,6 +3086,10 @@ msgstr "Il file non può essere più grande di {APP_DOCUMENT_UPLOAD_SIZE_LIMIT} msgid "File size exceeds the limit of {APP_DOCUMENT_UPLOAD_SIZE_LIMIT} MB" msgstr "La dimensione del file supera il limite di {APP_DOCUMENT_UPLOAD_SIZE_LIMIT} MB" +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Fill in the details to create a new subscription claim." +msgstr "" + #: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx #: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx #: packages/ui/primitives/document-flow/field-items-advanced-settings/name-field.tsx @@ -3036,6 +3107,10 @@ msgstr "Per qualsiasi domanda riguardante questa divulgazione, le firme elettron msgid "For each recipient, provide their email (required) and name (optional) in separate columns. Download the template CSV below for the correct format." msgstr "Per ogni destinatario, fornisci la loro email (obbligatoria) e il nome (opzionale) in colonne separate. Scarica il modello CSV qui sotto per il formato corretto." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "For example, if the claim has a new flag \"FLAG_1\" set to true, then this organisation will get that flag added." +msgstr "" + #: packages/lib/server-only/auth/send-forgot-password.ts msgid "Forgot Password?" msgstr "Password dimenticata?" @@ -3046,6 +3121,10 @@ msgstr "Password dimenticata?" msgid "Forgot your password?" msgstr "Hai dimenticato la tua password?" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Free" +msgstr "" + #: packages/ui/primitives/document-flow/types.ts msgid "Free Signature" msgstr "Firma gratuita" @@ -3079,6 +3158,7 @@ msgid "Global recipient action authentication" msgstr "Autenticazione globale del destinatario" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "Go back" msgstr "" @@ -3193,7 +3273,6 @@ msgid "Here you can manage your password and security settings." msgstr "Qui puoi gestire la tua password e le impostazioni di sicurezza." #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx -#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx msgid "Here you can set preferences and defaults for branding." msgstr "Qui puoi impostare preferenze e valori predefiniti per il branding." @@ -3260,6 +3339,14 @@ msgstr "Sono il proprietario di questo documento" msgid "I'm sure! Delete it" msgstr "Sono sicuro! Eliminalo" +#: apps/remix/app/components/tables/admin-claims-table.tsx +msgid "ID" +msgstr "" + +#: apps/remix/app/components/tables/admin-claims-table.tsx +msgid "ID copied to clipboard" +msgstr "" + #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx msgid "If you do not want to use the authenticator prompted, you can close it, which will then display the next available authenticator." msgstr "Se non vuoi utilizzare l'autenticatore richiesto, puoi chiuderlo, dopodiché verrà mostrato il successivo disponibile." @@ -3298,6 +3385,7 @@ msgstr "Ereditare metodo di autenticazione" #: apps/remix/app/components/forms/document-preferences-form.tsx #: apps/remix/app/components/forms/document-preferences-form.tsx #: apps/remix/app/components/forms/document-preferences-form.tsx +#: apps/remix/app/components/forms/document-preferences-form.tsx msgid "Inherit from organisation" msgstr "" @@ -3305,6 +3393,11 @@ msgstr "" msgid "Inherit organisation members" msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Inherited subscription claim" +msgstr "" + #: apps/remix/app/components/general/document-signing/document-signing-initials-field.tsx #: packages/ui/primitives/document-flow/types.ts msgid "Initials" @@ -3385,7 +3478,7 @@ msgstr "" msgid "Invited At" msgstr "Invitato il" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx msgid "Invoice" msgstr "Fattura" @@ -3479,7 +3572,7 @@ msgstr "Ultimo utilizzo" msgid "Leaderboard" msgstr "Classifica" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/dialogs/organisation-leave-dialog.tsx msgid "Leave" msgstr "Lascia" @@ -3541,8 +3634,10 @@ msgstr "Caricamento in corso..." msgid "Login" msgstr "Accedi" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/tables/organisation-teams-table.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Manage" msgstr "Gestisci" @@ -3555,10 +3650,18 @@ msgstr "Gestisci il profilo di {0}" msgid "Manage all organisations you are currently associated with." msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/claims.tsx +msgid "Manage all subscription claims" +msgstr "" + #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/templates.$id._index.tsx msgid "Manage and view template" msgstr "Gestisci e visualizza il modello" +#: apps/remix/app/components/general/organisations/organisation-billing-portal-button.tsx +msgid "Manage billing" +msgstr "" + #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx msgid "Manage details for this public template" msgstr "Gestisci i dettagli per questo modello pubblico" @@ -3571,6 +3674,14 @@ msgstr "Gestisci Link Diretto" msgid "Manage documents" msgstr "Gestisci documenti" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Manage organisation" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx +msgid "Manage organisations" +msgstr "" + #: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx msgid "Manage passkeys" msgstr "Gestisci chiavi di accesso" @@ -3579,17 +3690,20 @@ msgstr "Gestisci chiavi di accesso" msgid "Manage permissions and access controls" msgstr "" -#: apps/remix/app/components/general/teams/team-billing-portal-button.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "Manage subscription" msgstr "Gestisci abbonamento" -#: apps/remix/app/components/general/billing-portal-button.tsx -msgid "Manage Subscription" +#. placeholder {0}: organisation.name +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Manage the {0} organisation" msgstr "" -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx -msgid "Manage subscriptions" -msgstr "Gestisci abbonamenti" +#. placeholder {1}: organisation.name +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Manage the {1} organisation subscription" +msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups._index.tsx msgid "Manage the custom groups of members for your organisation." @@ -3661,13 +3775,19 @@ msgstr "" msgid "Maximum file size: 4MB. Maximum 100 rows per upload. Blank values will use template defaults." msgstr "Dimensione massima del file: 4MB. Massimo 100 righe per caricamento. I valori vuoti utilizzeranno i valori predefiniti del modello." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/dialogs/team-member-create-dialog.tsx #: packages/lib/constants/teams.ts #: packages/lib/constants/organisations.ts msgid "Member" msgstr "Membro" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Member Count" +msgstr "" + +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/tables/organisation-members-table.tsx msgid "Member Since" msgstr "Membro dal" @@ -3698,7 +3818,12 @@ msgstr "" msgid "Modify recipients" msgstr "Modifica destinatari" +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Modify the details of the subscription claim." +msgstr "" + #: apps/remix/app/components/general/billing-plans.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Monthly" msgstr "Mensile" @@ -3716,9 +3841,11 @@ msgstr "Utenti attivi mensili: Utenti con almeno uno dei loro documenti completa #: apps/remix/app/components/tables/admin-leaderboard-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-dashboard-users-table.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx #: apps/remix/app/components/general/claim-account.tsx #: apps/remix/app/components/general/document-signing/document-signing-name-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx @@ -3771,7 +3898,6 @@ msgstr "" msgid "New Template" msgstr "Nuovo modello" -#: apps/remix/app/components/forms/signup.tsx #: apps/remix/app/components/embed/embed-document-signing-page.tsx #: apps/remix/app/components/embed/embed-direct-template-client-page.tsx #: apps/remix/app/components/dialogs/team-member-create-dialog.tsx @@ -3846,6 +3972,10 @@ msgstr "Nessun risultato trovato." msgid "No signature field found" msgstr "Nessun campo di firma trovato" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "No Stripe customer attached" +msgstr "" + #: apps/remix/app/components/tables/team-groups-table.tsx msgid "No team groups found" msgstr "" @@ -3873,6 +4003,7 @@ msgstr "Nessun valore trovato." msgid "No worries, it happens! Enter your email and we'll email you a special link to reset your password." msgstr "Non ti preoccupare, succede! Inserisci la tua email e ti invieremo un link speciale per reimpostare la tua password." +#: apps/remix/app/components/tables/admin-claims-table.tsx #: packages/lib/constants/document.ts msgid "None" msgstr "Nessuno" @@ -3897,6 +4028,16 @@ msgstr "Numero" msgid "Number format" msgstr "Formato numero" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Number of members allowed. 0 = Unlimited" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Number of teams allowed. 0 = Unlimited" +msgstr "" + #. placeholder {0}: document.team?.name #: apps/remix/app/components/general/document-signing/document-signing-page-view.tsx msgid "on behalf of \"{0}\" has invited you to approve this document" @@ -3957,10 +4098,6 @@ msgstr "Solo gli amministratori possono accedere e visualizzare il documento" msgid "Only managers and above can access and view the document" msgstr "Solo i manager e superiori possono accedere e visualizzare il documento" -#: apps/remix/app/components/forms/signup.tsx -msgid "Only subscribers can have a username shorter than 6 characters" -msgstr "Solo gli abbonati possono avere un nome utente più corto di 6 caratteri" - #: apps/remix/app/routes/_profile+/_layout.tsx #: apps/remix/app/components/general/generic-error-layout.tsx #: apps/remix/app/components/general/generic-error-layout.tsx @@ -3980,7 +4117,8 @@ msgstr "Oppure" msgid "Or continue with" msgstr "Oppure continua con" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Organisation" msgstr "" @@ -3996,6 +4134,11 @@ msgstr "" msgid "Organisation Group Settings" msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Organisation has been updated successfully" +msgstr "" + #: apps/remix/app/routes/_unauthenticated+/organisation.invite.$token.tsx msgid "Organisation invitation" msgstr "" @@ -4015,15 +4158,18 @@ msgid "Organisation Member" msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.members.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "Organisation Members" msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/forms/organisation-update-form.tsx #: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Organisation Name" msgstr "" #: apps/remix/app/routes/_authenticated+/_layout.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "Organisation not found" msgstr "" @@ -4036,6 +4182,7 @@ msgid "Organisation role" msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/components/dialogs/organisation-member-invite-dialog.tsx msgid "Organisation Role" msgstr "" @@ -4047,13 +4194,18 @@ msgstr "" msgid "Organisation Settings" msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Organisation Teams" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/forms/organisation-update-form.tsx -#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Organisation URL" msgstr "" #: apps/remix/app/routes/_authenticated+/dashboard.tsx #: apps/remix/app/routes/_authenticated+/settings+/organisations.tsx +#: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx #: apps/remix/app/components/general/settings-nav-mobile.tsx #: apps/remix/app/components/general/settings-nav-desktop.tsx #: apps/remix/app/components/general/menu-switcher.tsx @@ -4076,8 +4228,9 @@ msgstr "Altrimenti, il documento sarà creato come bozza." #: apps/remix/app/routes/_authenticated+/dashboard.tsx #: apps/remix/app/routes/_authenticated+/dashboard.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/tables/organisation-members-table.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Owner" msgstr "Proprietario" @@ -4092,10 +4245,6 @@ msgstr "Pagina {0} di {1}" msgid "Page {0} of {numPages}" msgstr "Pagina {0} di {numPages}" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx -msgid "Paid" -msgstr "Pagato" - #: apps/remix/app/components/forms/signin.tsx msgid "Passkey" msgstr "Password" @@ -4166,20 +4315,11 @@ msgstr "Password aggiornato" msgid "Password updated!" msgstr "Password aggiornata!" -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx -msgid "Pay" -msgstr "Paga" - -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "Payment is required to finalise the creation of your team." -msgstr "È necessario il pagamento per completare la creazione del tuo team." - -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Payment overdue" msgstr "Pagamento scaduto" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.teams.tsx #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.members.tsx #: apps/remix/app/components/general/template/template-page-view-documents-table.tsx #: apps/remix/app/components/general/document/document-status.tsx @@ -4205,9 +4345,15 @@ msgstr "Documenti in sospeso" msgid "Pending invitations" msgstr "Inviti in sospeso" -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx -msgid "Pending team deleted." -msgstr "Team in sospeso eliminato." +#: apps/remix/app/components/general/billing-plans.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "per month" +msgstr "" + +#: apps/remix/app/components/general/billing-plans.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "per year" +msgstr "" #: apps/remix/app/components/general/menu-switcher.tsx msgid "Personal Account" @@ -4369,6 +4515,10 @@ msgstr "Per favore, digita {0} per confermare" msgid "Please type <0>{0} to confirm." msgstr "Si prega di digitare <0>{0} per confermare." +#: apps/remix/app/components/forms/branding-preferences-form.tsx +msgid "Please upload a logo" +msgstr "" + #: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx msgid "Pre-formatted CSV template with example data." msgstr "Modello CSV preformattato con dati di esempio." @@ -4435,10 +4585,6 @@ msgstr "Profilo pubblico" msgid "Public profile URL" msgstr "URL del profilo pubblico" -#: apps/remix/app/components/forms/signup.tsx -msgid "Public profile username" -msgstr "Nome utente del profilo pubblico" - #: apps/remix/app/components/tables/templates-table.tsx msgid "Public templates are connected to your public profile. Any modifications to public templates will also appear in your public profile." msgstr "I modelli pubblici sono collegati al tuo profilo pubblico. Ogni modifica ai modelli pubblici apparirà anche nel tuo profilo pubblico." @@ -4621,7 +4767,6 @@ msgstr "Promemoria: per favore {recipientActionVerb} questo documento" msgid "Reminder: Please {recipientActionVerb} your document" msgstr "Promemoria: per favore {recipientActionVerb} il tuo documento" -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx #: apps/remix/app/components/tables/team-members-table.tsx #: apps/remix/app/components/tables/team-groups-table.tsx #: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx @@ -4630,7 +4775,7 @@ msgstr "Promemoria: per favore {recipientActionVerb} il tuo documento" #: apps/remix/app/components/general/teams/team-email-dropdown.tsx #: apps/remix/app/components/general/document-signing/document-signing-field-container.tsx #: apps/remix/app/components/general/document-signing/document-signing-field-container.tsx -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx #: apps/remix/app/components/forms/avatar-image.tsx #: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx #: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx @@ -4710,11 +4855,11 @@ msgstr "Reimposta password" msgid "Resetting Password..." msgstr "Reimpostazione della password..." -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Resolve" msgstr "Risolvi" -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Resolve payment" msgstr "Risolvere il pagamento" @@ -4754,7 +4899,7 @@ msgstr "Revoca" msgid "Revoke access" msgstr "Revoca l'accesso" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/tables/team-members-table.tsx #: apps/remix/app/components/tables/team-groups-table.tsx #: apps/remix/app/components/tables/organisation-members-table.tsx @@ -4764,7 +4909,6 @@ msgstr "Revoca l'accesso" #: apps/remix/app/components/dialogs/team-member-update-dialog.tsx #: apps/remix/app/components/dialogs/team-group-update-dialog.tsx #: apps/remix/app/components/dialogs/organisation-member-update-dialog.tsx -#: apps/remix/app/components/dialogs/organisation-member-invite-dialog.tsx msgid "Role" msgstr "Ruolo" @@ -4797,6 +4941,14 @@ msgstr "Salva modello" msgid "Search" msgstr "Cerca" +#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx +msgid "Search and manage all organisations" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/claims.tsx +msgid "Search by claim ID or name" +msgstr "" + #: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx msgid "Search by document title" msgstr "Cerca per titolo del documento" @@ -4806,6 +4958,10 @@ msgstr "Cerca per titolo del documento" msgid "Search by name or email" msgstr "Cerca per nome o email" +#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx +msgid "Search by organisation ID, name, customer ID or owner email" +msgstr "" + #: apps/remix/app/components/general/document/document-search.tsx msgid "Search documents..." msgstr "Cerca documenti..." @@ -4834,6 +4990,14 @@ msgstr "Attività di sicurezza" msgid "Select" msgstr "Seleziona" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Select a plan" +msgstr "" + +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Select a plan to continue" +msgstr "" + #: apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx msgid "Select a team to view its dashboard" msgstr "" @@ -4889,6 +5053,10 @@ msgstr "" msgid "Select passkey" msgstr "Seleziona una chiave di accesso" +#: apps/remix/app/components/forms/document-preferences-form.tsx +msgid "Select signature types" +msgstr "" + #: apps/remix/app/components/dialogs/organisation-group-create-dialog.tsx msgid "Select the members to add to this group" msgstr "" @@ -5182,10 +5350,6 @@ msgstr "Firma" msgid "Signing Certificate" msgstr "Certificato di Firma" -#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx -msgid "Signing certificate provided by" -msgstr "Certificato di firma fornito da" - #: packages/lib/server-only/document/send-completed-email.ts #: packages/lib/server-only/document/send-completed-email.ts msgid "Signing Complete!" @@ -5243,24 +5407,23 @@ msgstr "Alcuni firmatari non hanno un campo firma assegnato. Assegna almeno 1 ca #: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx #: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.public-profile.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.groups.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.groups.tsx -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/organisation-member-invites-table.tsx #: apps/remix/app/components/tables/organisation-member-invites-table.tsx #: apps/remix/app/components/tables/documents-table-action-dropdown.tsx #: apps/remix/app/components/tables/documents-table-action-button.tsx -#: apps/remix/app/components/general/billing-portal-button.tsx #: apps/remix/app/components/general/billing-plans.tsx -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx #: apps/remix/app/components/general/teams/team-email-usage.tsx #: apps/remix/app/components/general/teams/team-email-dropdown.tsx -#: apps/remix/app/components/general/teams/team-billing-portal-button.tsx #: apps/remix/app/components/general/organisations/organisation-invitations.tsx #: apps/remix/app/components/general/organisations/organisation-invitations.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-portal-button.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx #: apps/remix/app/components/general/document-signing/document-signing-auth-page.tsx #: apps/remix/app/components/general/document-signing/document-signing-auth-account.tsx #: apps/remix/app/components/general/document/document-page-view-dropdown.tsx @@ -5269,7 +5432,6 @@ msgstr "Alcuni firmatari non hanno un campo firma assegnato. Assegna almeno 1 ca #: apps/remix/app/components/general/document/document-audit-log-download-button.tsx #: apps/remix/app/components/general/direct-template/direct-template-signing-auth-page.tsx #: apps/remix/app/components/general/direct-template/direct-template-page.tsx -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx #: apps/remix/app/components/embed/embed-document-signing-page.tsx #: apps/remix/app/components/embed/embed-direct-template-client-page.tsx #: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx @@ -5279,8 +5441,6 @@ msgstr "Alcuni firmatari non hanno un campo firma assegnato. Assegna almeno 1 ca #: apps/remix/app/components/dialogs/template-create-dialog.tsx #: apps/remix/app/components/dialogs/team-email-delete-dialog.tsx #: apps/remix/app/components/dialogs/team-email-delete-dialog.tsx -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx #: apps/remix/app/components/dialogs/organisation-member-invite-dialog.tsx #: apps/remix/app/components/dialogs/document-resend-dialog.tsx #: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx @@ -5347,9 +5507,8 @@ msgid "Stats" msgstr "Statistiche" #: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx #: apps/remix/app/components/tables/documents-table.tsx #: apps/remix/app/components/general/template/template-page-view-documents-table.tsx msgid "Status" @@ -5359,6 +5518,18 @@ msgstr "Stato" msgid "Step <0>{step} of {maxStep}" msgstr "Passo <0>{step} di {maxStep}" +#: apps/remix/app/components/tables/admin-organisations-table.tsx +msgid "Stripe" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Stripe customer created successfully" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Stripe Customer ID" +msgstr "" + #: packages/ui/primitives/template-flow/add-template-settings.tsx #: packages/ui/primitives/document-flow/add-subject.tsx msgid "Subject <0>(Optional)" @@ -5368,17 +5539,32 @@ msgstr "Oggetto <0>(Opzionale)" msgid "Subscribe" msgstr "" -#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Subscription" msgstr "Abbonamento" -#: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx -msgid "Subscriptions" -msgstr "Abbonamenti" +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Subscription claim created successfully." +msgstr "" + +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "Subscription claim deleted successfully." +msgstr "" + +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Subscription claim updated successfully." +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/claims.tsx +msgid "Subscription Claims" +msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/organisation-member-invites-table.tsx @@ -5440,8 +5626,8 @@ msgid "System Theme" msgstr "Tema del sistema" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/tables/organisation-teams-table.tsx -#: apps/remix/app/components/tables/organisation-pending-teams-table.tsx msgid "Team" msgstr "Squadra" @@ -5458,9 +5644,10 @@ msgstr "" msgid "Team Assignments" msgstr "" -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "Team checkout" -msgstr "Acquisto squadra" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Team Count" +msgstr "" #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx @@ -5570,6 +5757,10 @@ msgstr "Impostazioni del team" msgid "Team templates" msgstr "Modelli del team" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Team url" +msgstr "" + #: apps/remix/app/components/forms/team-update-form.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx msgid "Team URL" @@ -5587,7 +5778,7 @@ msgstr "Team" msgid "Teams help you organize your work and collaborate with others. Create your first team to get started." msgstr "" -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Teams restricted" msgstr "Team limitati" @@ -5764,6 +5955,12 @@ msgstr "" msgid "The organisation role that will be applied to all members in this group." msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "" +"The organisation you are looking for may have been removed, renamed or may have never\n" +" existed." +msgstr "" + #: apps/remix/app/routes/_authenticated+/_layout.tsx msgid "" "The organisation you are looking for may have been removed, renamed or may have never\n" @@ -5936,6 +6133,10 @@ msgstr "Questa azione è reversibile, ma fai attenzione poiché l'account potreb msgid "This can be overriden by setting the authentication requirements directly on each recipient in the next step." msgstr "Questo può essere sovrascritto impostando i requisiti di autenticazione direttamente su ciascun destinatario nel passaggio successivo." +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "This claim is locked and cannot be deleted." +msgstr "" + #: packages/email/template-components/template-document-super-delete.tsx msgid "This document can not be recovered, if you would like to dispute the reason for future documents please contact support." msgstr "Questo documento non può essere recuperato, se vuoi contestare la ragione per i documenti futuri, contatta il supporto." @@ -6020,6 +6221,10 @@ msgstr "Questo campo non può essere modificato o eliminato. Quando condividi il msgid "This is how the document will reach the recipients once the document is ready for signing." msgstr "È così che il documento raggiungerà i destinatari una volta pronto per essere firmato." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "This is the claim that this organisation was initially created with. Any feature flag changes to this claim will be backported into this organisation." +msgstr "" + #: apps/remix/app/routes/_unauthenticated+/team.verify.transfer.token.tsx msgid "This link is invalid or has expired." msgstr "" @@ -6040,10 +6245,6 @@ msgstr "Questa chiave d'accesso è già stata registrata." msgid "This passkey is not configured for this application. Please login and add one in the user settings." msgstr "Questa chiave di accesso non è configurata per questa applicazione. Effettua il login e aggiungine una nelle impostazioni utente." -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "This price includes minimum 5 seats." -msgstr "Questo prezzo include almeno 5 posti." - #: packages/ui/primitives/document-flow/add-fields.tsx msgid "This recipient can no longer be modified as they have signed a field, or completed the document." msgstr "Questo destinatario non può più essere modificato poiché ha firmato un campo o completato il documento." @@ -6075,14 +6276,9 @@ msgstr "Questo token è invalido o è scaduto. Si prega di contattare il vostro #: apps/remix/app/components/forms/team-update-form.tsx #: apps/remix/app/components/forms/organisation-update-form.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx -#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "This URL is already in use." msgstr "Questo URL è già in uso." -#: apps/remix/app/components/forms/signup.tsx -msgid "This username has already been taken" -msgstr "Questo nome utente è già stato preso" - #: packages/ui/components/document/document-email-checkboxes.tsx msgid "This will be sent to all recipients if a pending document has been deleted." msgstr "Questo sarà inviato a tutti i destinatari se un documento in attesa è stato eliminato." @@ -6095,6 +6291,10 @@ msgstr "Questo sarà inviato a tutti i destinatari una volta che il documento è msgid "This will be sent to the document owner once the document has been fully completed." msgstr "Questo sarà inviato al proprietario del documento una volta che il documento è stato completamente completato." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "This will ONLY backport feature flags which are set to true, anything disabled in the initial claim will not be backported" +msgstr "" + #: packages/ui/components/recipient/recipient-action-auth-select.tsx msgid "This will override any global settings." msgstr "Questo sovrascriverà qualsiasi impostazione globale." @@ -6385,20 +6585,27 @@ msgstr "Incompleto" msgid "Unknown" msgstr "Sconosciuto" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx -msgid "Unpaid" -msgstr "Non pagato" +#: apps/remix/app/components/tables/admin-claims-table.tsx +msgid "Unlimited" +msgstr "" + +#: packages/lib/types/subscription.ts +msgid "Unlimited documents, API and more" +msgstr "" #: apps/remix/app/components/dialogs/team-group-create-dialog.tsx msgid "Untitled Group" msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx #: apps/remix/app/components/forms/public-profile-form.tsx #: apps/remix/app/components/forms/document-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx #: apps/remix/app/components/dialogs/team-member-update-dialog.tsx #: apps/remix/app/components/dialogs/team-group-update-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx @@ -6413,6 +6620,14 @@ msgstr "Aggiorna" msgid "Update Banner" msgstr "Aggiorna banner" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx +msgid "Update Billing" +msgstr "" + +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Update Claim" +msgstr "" + #: apps/remix/app/components/forms/organisation-update-form.tsx msgid "Update organisation" msgstr "" @@ -6445,6 +6660,10 @@ msgstr "Aggiorna destinatario" msgid "Update role" msgstr "Aggiorna ruolo" +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Update Subscription Claim" +msgstr "" + #: apps/remix/app/components/forms/team-update-form.tsx msgid "Update team" msgstr "Aggiorna team" @@ -6524,7 +6743,7 @@ msgstr "Carica Firma" msgid "Upload Template Document" msgstr "Carica Documento Modello" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Upload your brand logo (max 5MB, JPG, PNG, or WebP)" msgstr "Carica il tuo logo aziendale (max 5MB, JPG, PNG o WebP)" @@ -6572,10 +6791,6 @@ msgstr "Utente" msgid "User has no password." msgstr "L'utente non ha password." -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx -msgid "User ID" -msgstr "ID Utente" - #: apps/remix/app/components/dialogs/admin-user-enable-dialog.tsx #: apps/remix/app/components/dialogs/admin-user-disable-dialog.tsx #: apps/remix/app/components/dialogs/admin-user-delete-dialog.tsx @@ -6590,10 +6805,6 @@ msgstr "I profili utente sono qui!" msgid "User with this email already exists. Please use a different email address." msgstr "Un utente con questo email esiste già. Si prega di utilizzare un indirizzo email diverso." -#: apps/remix/app/components/forms/signup.tsx -msgid "Username can only container alphanumeric characters and dashes." -msgstr "Il nome utente può contenere solo caratteri alfanumerici e trattini." - #: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx msgid "Users" msgstr "Utenti" @@ -6641,7 +6852,7 @@ msgstr "Verifica il tuo indirizzo email del team" msgid "Version History" msgstr "Cronologia delle versioni" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx #: apps/remix/app/components/tables/inbox-table.tsx #: apps/remix/app/components/tables/documents-table-action-dropdown.tsx #: apps/remix/app/components/tables/documents-table-action-button.tsx @@ -6709,6 +6920,10 @@ msgstr "Vedi di più" msgid "View Original Document" msgstr "Visualizza Documento Originale" +#: apps/remix/app/components/tables/admin-organisations-table.tsx +msgid "View owner" +msgstr "" + #: packages/email/template-components/template-document-self-signed.tsx msgid "View plans" msgstr "Visualizza piani" @@ -6778,9 +6993,8 @@ msgstr "Vuoi il tuo profilo pubblico?" msgid "Warning: Assistant as last signer" msgstr "" -#: apps/remix/app/components/general/billing-portal-button.tsx -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx -#: apps/remix/app/components/general/teams/team-billing-portal-button.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-portal-button.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "We are unable to proceed to the billing portal at this time. Please try again, or contact support." msgstr "Non siamo in grado di procedere al portale di fatturazione in questo momento. Per favore riprova, o contatta l'assistenza." @@ -6792,10 +7006,19 @@ msgstr "Non siamo in grado di rimuovere questa chiave d'accesso al momento. Per msgid "We are unable to update this passkey at the moment. Please try again later." msgstr "Non siamo in grado di aggiornare questa chiave d'accesso al momento. Per favore riprova più tardi." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "We couldn't create a Stripe customer. Please try again." +msgstr "" + #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx msgid "We couldn't update the group. Please try again." msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "We couldn't update the organisation. Please try again." +msgstr "" + #: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx msgid "We encountered an error while removing the direct template link. Please try again later." msgstr "Abbiamo riscontrato un errore durante la rimozione del link diretto al modello. Per favore riprova più tardi." @@ -6829,10 +7052,6 @@ msgstr "Abbiamo riscontrato un errore sconosciuto mentre tentavamo di creare un msgid "We encountered an unknown error while attempting to delete it. Please try again later." msgstr "Abbiamo riscontrato un errore sconosciuto durante il tentativo di eliminarlo. Per favore riprova più tardi." -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx -msgid "We encountered an unknown error while attempting to delete the pending team. Please try again later." -msgstr "Abbiamo riscontrato un errore sconosciuto mentre tentavamo di eliminare il team in sospeso. Per favore riprova più tardi." - #: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx msgid "We encountered an unknown error while attempting to delete this organisation. Please try again later." msgstr "" @@ -6943,10 +7162,6 @@ msgstr "Abbiamo riscontrato un errore sconosciuto durante il tentativo di aggior msgid "We have sent a confirmation email for verification." msgstr "Abbiamo inviato un'email di conferma per la verifica." -#: apps/remix/app/components/forms/signup.tsx -msgid "We need a username to create your profile" -msgstr "Abbiamo bisogno di un nome utente per creare il tuo profilo" - #: apps/remix/app/components/forms/signup.tsx msgid "We need your signature to sign documents" msgstr "Abbiamo bisogno della tua firma per firmare i documenti" @@ -6959,10 +7174,6 @@ msgstr "Non siamo riusciti a copiare il token negli appunti. Si prega di riprova msgid "We were unable to copy your recovery code to your clipboard. Please try again." msgstr "Non siamo riusciti a copiare il tuo codice di recupero negli appunti. Si prega di riprovare." -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "We were unable to create a checkout session. Please try again, or contact support" -msgstr "Non siamo riusciti a creare una sessione di pagamento. Si prega di riprovare o contattare il supporto" - #: apps/remix/app/components/forms/signup.tsx msgid "We were unable to create your account. Please review the information you provided and try again." msgstr "Non siamo riusciti a creare il tuo account. Si prega di rivedere le informazioni fornite e riprovare." @@ -6992,7 +7203,7 @@ msgstr "Non siamo riusciti a impostare l'autenticazione a due fattori per il tuo msgid "We were unable to submit this document at this time. Please try again later." msgstr "Non siamo riusciti a inviare questo documento in questo momento. Per favore riprova più tardi." -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx msgid "We were unable to update your branding preferences at this time, please try again later" msgstr "Non siamo riusciti ad aggiornare le tue preferenze di branding al momento, riprova più tardi" @@ -7065,10 +7276,6 @@ msgstr "URL del webhook" msgid "Webhooks" msgstr "Webhook" -#: apps/remix/app/components/general/billing-plans.tsx -msgid "Weekly" -msgstr "" - #: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx msgid "Welcome" msgstr "Benvenuto" @@ -7114,6 +7321,10 @@ msgstr "Quando utilizzi la nostra piattaforma per apporre la tua firma elettroni msgid "While waiting for them to do so you can create your own Documenso account and get started with document signing right away." msgstr "Mentre aspetti che lo facciano, puoi creare il tuo account Documenso e iniziare a firmare documenti subito." +#: packages/lib/types/subscription.ts +msgid "Whitelabeling, unlimited members and more" +msgstr "" + #: apps/remix/app/components/dialogs/document-resend-dialog.tsx msgid "Who do you want to remind?" msgstr "Chi vuoi ricordare?" @@ -7131,6 +7342,7 @@ msgid "Write about yourself" msgstr "Scrivi di te stesso" #: apps/remix/app/components/general/billing-plans.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Yearly" msgstr "Annuale" @@ -7200,6 +7412,10 @@ msgstr "Stai per revocare l'accesso per il team <0>{0} ({1}) per utilizzare msgid "You are about to send this document to the recipients. Are you sure you want to continue?" msgstr "Stai per inviare questo documento ai destinatari. Sei sicuro di voler continuare?" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "You are currently on the <0>Free Plan." +msgstr "" + #: apps/remix/app/components/dialogs/team-member-update-dialog.tsx msgid "You are currently updating <0>{memberName}." msgstr "" @@ -7306,12 +7522,12 @@ msgstr "Non puoi caricare documenti in questo momento." msgid "You cannot upload encrypted PDFs" msgstr "Non puoi caricare PDF crittografati" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx -msgid "You currently have no access to any teams within this organisation. Please contact your organisation to request access." +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "You currently have an inactive <0>{currentProductName} subscription" msgstr "" -#: apps/remix/app/components/general/billing-portal-button.tsx -msgid "You do not currently have a customer record, this should not happen. Please contact support for assistance." +#: apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx +msgid "You currently have no access to any teams within this organisation. Please contact your organisation to request access." msgstr "" #: apps/remix/app/components/forms/token.tsx @@ -7346,10 +7562,6 @@ msgstr "Sei stato invitato a unirti a {0} su Documenso" msgid "You have been invited to join the following organisation" msgstr "" -#: packages/email/templates/organisation-invite.tsx -msgid "You have been invited to join the following team" -msgstr "Sei stato invitato a unirti al seguente team" - #: packages/lib/server-only/recipient/set-document-recipients.ts #: packages/lib/server-only/recipient/delete-document-recipient.ts msgid "You have been removed from a document" @@ -7383,6 +7595,10 @@ msgstr "Non hai ancora creato o ricevuto documenti. Per creare un documento cari msgid "You have reached the maximum limit of {0} direct templates. <0>Upgrade your account to continue!" msgstr "Hai raggiunto il limite massimo di {0} modelli diretti. <0>Aggiorna il tuo account per continuare!" +#: apps/remix/app/components/dialogs/team-create-dialog.tsx +msgid "You have reached the maximum number of teams for your plan. Please contact sales at <0>support@documenso.com if you would like to adjust your plan." +msgstr "" + #: apps/remix/app/components/general/document/document-upload.tsx msgid "You have reached your document limit for this month. Please upgrade your plan." msgstr "Hai raggiunto il limite dei documenti per questo mese. Si prega di aggiornare il proprio piano." @@ -7482,10 +7698,6 @@ msgstr "Devi essere loggato per visualizzare questa pagina." msgid "You need to setup 2FA to mark this document as viewed." msgstr "Devi configurare l'autenticazione a due fattori per contrassegnare questo documento come visualizzato." -#: apps/remix/app/components/forms/signup.tsx -msgid "You will get notified & be able to set up your documenso public profile when we launch the feature." -msgstr "Riceverai una notifica e potrai configurare il tuo profilo pubblico su Documenso quando lanceremo la funzionalità." - #: apps/remix/app/components/forms/2fa/enable-authenticator-app-dialog.tsx msgid "You will now be required to enter a code from your authenticator app when signing in." msgstr "Ora ti verrà richiesto di inserire un codice dalla tua app di autenticazione durante l'accesso." @@ -7506,11 +7718,11 @@ msgstr "Il tuo avatar è stato aggiornato correttamente." msgid "Your banner has been updated successfully." msgstr "Il tuo banner è stato aggiornato correttamente." -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Your brand website URL" msgstr "URL del sito web del tuo marchio" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx msgid "Your branding preferences have been updated" msgstr "Le tue preferenze di branding sono state aggiornate" @@ -7522,6 +7734,18 @@ msgstr "Il tuo invio massivo è stato avviato. Riceverai una notifica via email msgid "Your bulk send operation for template \"{templateName}\" has completed." msgstr "La tua operazione di invio massivo per il modello \"{templateName}\" è stata completata." +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "Your current {currentProductName} plan is past due. Please update your payment information." +msgstr "" + +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "Your current plan is inactive." +msgstr "" + +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "Your current plan is past due." +msgstr "" + #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx msgid "Your direct signing templates" msgstr "I tuoi modelli di firma diretta" @@ -7611,7 +7835,7 @@ msgstr "La tua password è stata aggiornata con successo." msgid "Your password has been updated." msgstr "La tua password è stata aggiornata." -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Your payment for teams is overdue. Please settle the payment to avoid any service disruptions." msgstr "Il pagamento per i team è in ritardo. Si prega di effettuare il pagamento per evitare interruzioni del servizio." diff --git a/packages/lib/translations/pl/web.po b/packages/lib/translations/pl/web.po index 1201550ac..055cec8e0 100644 --- a/packages/lib/translations/pl/web.po +++ b/packages/lib/translations/pl/web.po @@ -73,11 +73,6 @@ msgstr "{0, plural, one {# znak przekroczony} few {# znaki przekroczone} many {# msgid "{0, plural, one {# recipient} other {# recipients}}" msgstr "{0, plural, one {# odbiorca} few {# odbiorców} many {# odbiorców} other {# odbiorców}}" -#. placeholder {0}: row.original.quantity -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx -msgid "{0, plural, one {# Seat} other {# Seats}}" -msgstr "{0, plural, one {# miejsce} few {# miejsca} many {# miejsc} other {# miejsc}}" - #. placeholder {0}: org.teams.length #: apps/remix/app/routes/_authenticated+/dashboard.tsx msgid "{0, plural, one {# team} other {# teams}}" @@ -132,16 +127,6 @@ msgstr "{0} zaprosił cię do {recipientActionVerb} dokument „{1}”." msgid "{0} invited you to {recipientActionVerb} a document" msgstr "{0} zaprosił Cię do {recipientActionVerb} dokument" -#. placeholder {0}: memberName || memberEmail -#: packages/email/templates/team-join.tsx -msgid "{0} joined the team {teamName} on Documenso" -msgstr "{0} dołączył do zespołu {teamName} na Documenso" - -#. placeholder {0}: memberName || memberEmail -#: packages/email/templates/team-leave.tsx -msgid "{0} left the team {teamName} on Documenso" -msgstr "{0} opuścił zespół {teamName} na Documenso" - #. placeholder {0}: remaining.documents #. placeholder {1}: quota.documents #: apps/remix/app/components/general/document/document-upload.tsx @@ -223,14 +208,6 @@ msgstr "{inviterName} w imieniu \"{teamName}\" zaprosił Cię do {0}<0/>\"{docum msgid "{inviterName} on behalf of \"{teamName}\" has invited you to {action} {documentName}" msgstr "{inviterName} w imieniu \"{teamName}\" zaprosił Cię do {action} {documentName}" -#: packages/email/templates/team-join.tsx -msgid "{memberEmail} joined the following team" -msgstr "{memberEmail} dołączył do następującego zespołu" - -#: packages/email/templates/team-leave.tsx -msgid "{memberEmail} left the following team" -msgstr "{memberEmail} opuścił następujący zespół" - #: packages/lib/utils/document-audit-logs.ts msgid "{prefix} added a field" msgstr "{prefix} dodał pole" @@ -484,6 +461,14 @@ msgstr "<0>Jesteś na drodze do ukończenia podpisywania \"<1>{documentTitle}You are about to complete viewing \"<1>{documentTitle}\".<2/> Are you sure?" msgstr "<0>Jesteś na drodze do zakończenia przeglądania \"<1>{documentTitle}\".<2/> Czy jesteś pewien?" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "0 Free organisations left" +msgstr "" + +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "1 Free organisations left" +msgstr "" + #: apps/remix/app/components/forms/token.tsx msgid "1 month" msgstr "1 miesiąc" @@ -510,6 +495,7 @@ msgid "404 Organisation group not found" msgstr "" #: apps/remix/app/routes/_authenticated+/_layout.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "404 Organisation not found" msgstr "" @@ -522,6 +508,14 @@ msgstr "404 Profil nie znaleziony" msgid "404 Team not found" msgstr "404 Zespół nie znaleziony" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "5 documents a month" +msgstr "" + +#: packages/lib/types/subscription.ts +msgid "5 Documents a month" +msgstr "" + #: apps/remix/app/components/general/generic-error-layout.tsx msgid "500 Internal Server Error" msgstr "" @@ -566,9 +560,29 @@ msgstr "Zaktualizowano pole" msgid "A means to print or download documents for your records" msgstr "Środek do drukowania lub pobierania dokumentów do swoich zapisów" -#: packages/lib/jobs/definitions/emails/send-team-member-joined-email.handler.ts -msgid "A new member has joined your team" -msgstr "Nowy członek dołączył do Twojego zespołu" +#: packages/email/templates/organisation-join.tsx +msgid "A member has joined your organisation on Documenso" +msgstr "" + +#: packages/lib/jobs/definitions/emails/send-organisation-member-left-email.handler.ts +msgid "A member has left your organisation" +msgstr "" + +#: packages/email/templates/organisation-leave.tsx +msgid "A member has left your organisation {organisationName}" +msgstr "" + +#: packages/email/templates/organisation-leave.tsx +msgid "A member has left your organisation on Documenso" +msgstr "" + +#: packages/lib/jobs/definitions/emails/send-organisation-member-joined-email.handler.ts +msgid "A new member has joined your organisation" +msgstr "" + +#: packages/email/templates/organisation-join.tsx +msgid "A new member has joined your organisation {organisationName}" +msgstr "" #: apps/remix/app/components/forms/token.tsx msgid "A new token was created successfully." @@ -608,19 +622,6 @@ msgstr "Sekret, który zostanie wysłany na Twój adres URL, abyś mógł zweryf msgid "A stable internet connection" msgstr "Stabilne połączenie internetowe" -#: packages/email/templates/team-join.tsx -msgid "A team member has joined a team on Documenso" -msgstr "Członek zespołu dołączył do zespołu na Documenso" - -#. placeholder {0}: team.name -#: packages/lib/jobs/definitions/emails/send-team-member-left-email.handler.ts -msgid "A team member has left {0}" -msgstr "Członek zespołu opuścił {0}" - -#: packages/email/templates/team-leave.tsx -msgid "A team member has left a team on Documenso" -msgstr "Członek zespołu opuścił zespół na Documenso" - #: packages/email/templates/team-delete.tsx #: packages/email/templates/team-delete.tsx msgid "A team you were a part of has been deleted" @@ -630,8 +631,11 @@ msgstr "Zespół, którego częścią byłeś, został usunięty" msgid "A unique URL to access your profile" msgstr "Unikalny URL do dostępu do Twojego profilu" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "A unique URL to identify the organisation" +msgstr "" + #: apps/remix/app/components/forms/organisation-update-form.tsx -#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "A unique URL to identify your organisation" msgstr "" @@ -651,8 +655,8 @@ msgid "Accept" msgstr "Akceptuj" #: packages/email/templates/organisation-invite.tsx -msgid "Accept invitation to join a team on Documenso" -msgstr "Akceptuj zaproszenie do dołączenia do zespołu na Documenso" +msgid "Accept invitation to join an organisation on Documenso" +msgstr "" #: packages/email/templates/confirm-team-email.tsx msgid "Accept team email request for {teamName} on Documenso" @@ -720,11 +724,12 @@ msgstr "Akcja" #: apps/remix/app/components/tables/organisation-groups-table.tsx #: apps/remix/app/components/tables/inbox-table.tsx #: apps/remix/app/components/tables/documents-table.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx #: apps/remix/app/components/general/template/template-page-view-documents-table.tsx msgid "Actions" msgstr "Akcje" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.teams.tsx #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.members.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx msgid "Active" @@ -865,11 +870,7 @@ msgstr "Dodaj osoby, które podpiszą dokument." msgid "Add the recipients to create the document with" msgstr "Dodaj odbiorców, aby utworzyć dokument" -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "Adding and removing seats will adjust your invoice accordingly." -msgstr "Dodawanie i usuwanie miejsc dostosuje fakturę odpowiednio." - -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Additional brand information to display at the bottom of emails" msgstr "Dodatkowe informacje o marce do wyświetlenia na dole wiadomości e-mail" @@ -886,6 +887,10 @@ msgstr "Akcje administratora" msgid "Admin panel" msgstr "Panel administratora" +#: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx +msgid "Admin Panel" +msgstr "" + #: packages/ui/primitives/template-flow/add-template-settings.tsx #: packages/ui/primitives/document-flow/add-settings.tsx msgid "Advanced Options" @@ -962,6 +967,10 @@ msgstr "" msgid "Allowed Signature Types" msgstr "" +#: apps/remix/app/components/tables/admin-claims-table.tsx +msgid "Allowed teams" +msgstr "" + #: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx msgid "Allows authenticating using biometrics, password managers, hardware keys, etc." msgstr "Pozwala na uwierzytelnianie za pomocą biometrii, menedżerów haseł, kluczy sprzętowych itp." @@ -970,7 +979,7 @@ msgstr "Pozwala na uwierzytelnianie za pomocą biometrii, menedżerów haseł, k msgid "Already have an account? <0>Sign in instead" msgstr "Masz już konto? <0>Zaloguj się zamiast tego" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx msgid "Amount" msgstr "Kwota" @@ -991,6 +1000,8 @@ msgid "An email containing an invitation will be sent to each member." msgstr "E-mail zawierający zaproszenie zostanie wysłany do każdego członka." #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/general/claim-account.tsx #: apps/remix/app/components/forms/token.tsx #: apps/remix/app/components/forms/signup.tsx @@ -1230,6 +1241,10 @@ msgstr "Zatwierdzanie" msgid "Are you sure you want to complete the document? This action cannot be undone. Please ensure that you have completed prefilling all relevant fields before proceeding." msgstr "" +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "Are you sure you want to delete the following claim?" +msgstr "" + #: apps/remix/app/components/dialogs/token-delete-dialog.tsx msgid "Are you sure you want to delete this token?" msgstr "Czy na pewno chcesz usunąć ten token?" @@ -1340,9 +1355,9 @@ msgid "Awaiting email confirmation" msgstr "Czekam na potwierdzenie e-maila" #: apps/remix/app/components/general/direct-template/direct-template-signing-form.tsx -#: apps/remix/app/components/forms/signup.tsx #: apps/remix/app/components/dialogs/team-member-create-dialog.tsx #: apps/remix/app/components/dialogs/team-group-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Back" msgstr "Powrót" @@ -1367,29 +1382,17 @@ msgstr "Kody zapasowe" msgid "Banner Updated" msgstr "Baner został zaktualizowany" -#: apps/remix/app/components/forms/signup.tsx -msgid "Basic details" -msgstr "Podstawowe szczegóły" - #: packages/email/template-components/template-confirmation-email.tsx msgid "Before you get started, please confirm your email address by clicking the button below:" msgstr "Zanim zaczniesz, proszę potwierdź swój adres e-mail, klikając przycisk poniżej:" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings._layout.tsx -#: apps/remix/app/routes/_authenticated+/settings+/billing.tsx -#: apps/remix/app/components/general/settings-nav-mobile.tsx -#: apps/remix/app/components/general/settings-nav-desktop.tsx #: apps/remix/app/components/general/teams/team-settings-nav-mobile.tsx #: apps/remix/app/components/general/teams/team-settings-nav-desktop.tsx msgid "Billing" msgstr "Fakturowanie" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx -#: apps/remix/app/routes/_authenticated+/settings+/billing.tsx -msgid "Billing has been moved to organisations" -msgstr "" - #: packages/ui/primitives/signature-pad/signature-pad-color-picker.tsx msgid "Black" msgstr "Czarny" @@ -1398,12 +1401,13 @@ msgstr "Czarny" msgid "Blue" msgstr "Niebieski" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx msgid "Branding Preferences" msgstr "Preferencje dotyczące marki" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx msgid "Branding preferences updated" msgstr "Preferencje dotyczące marki zaktualizowane" @@ -1512,7 +1516,7 @@ msgstr "" #: apps/remix/app/components/dialogs/team-email-add-dialog.tsx #: apps/remix/app/components/dialogs/team-delete-dialog.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx +#: apps/remix/app/components/dialogs/team-create-dialog.tsx #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx #: apps/remix/app/components/dialogs/organisation-member-update-dialog.tsx @@ -1523,9 +1527,13 @@ msgstr "" #: apps/remix/app/components/dialogs/organisation-group-create-dialog.tsx #: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx #: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx #: apps/remix/app/components/dialogs/document-resend-dialog.tsx #: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx #: apps/remix/app/components/dialogs/document-delete-dialog.tsx +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx #: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx #: packages/ui/primitives/signature-pad/signature-pad-dialog.tsx #: packages/ui/primitives/document-flow/send-document-action-dialog.tsx @@ -1574,7 +1582,7 @@ msgstr "Pole wyboru" msgid "Checkbox values" msgstr "Wartości checkboxa" -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Checkout" msgstr "Kasa" @@ -1598,13 +1606,9 @@ msgstr "Wybierz..." msgid "Claim account" msgstr "Zgłoś konto" -#: apps/remix/app/components/forms/signup.tsx -msgid "Claim username" -msgstr "Zgłoś nazwę użytkownika" - -#: apps/remix/app/components/forms/signup.tsx -msgid "Claim your username now" -msgstr "Zgłoś swoją nazwę użytkownika teraz" +#: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx +msgid "Claims" +msgstr "" #: apps/remix/app/components/dialogs/template-use-dialog.tsx msgid "Clear file" @@ -1660,6 +1664,10 @@ msgstr "Kliknij, aby wstawić pole" msgid "Close" msgstr "Zamknij" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Compare all plans and features in detail" +msgstr "" + #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx #: apps/remix/app/components/forms/signup.tsx @@ -1782,6 +1790,10 @@ msgstr "Zgoda na transakcje elektroniczne" msgid "Contact Information" msgstr "Informacje kontaktowe" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Contact sales here" +msgstr "" + #: apps/remix/app/routes/_authenticated+/admin+/site-settings.tsx msgid "Content" msgstr "Treść" @@ -1793,6 +1805,7 @@ msgstr "Treść" #: apps/remix/app/components/general/document-signing/document-signing-form.tsx #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx #: apps/remix/app/components/dialogs/assistant-confirmation-dialog.tsx #: packages/ui/primitives/document-flow/document-flow-root.tsx msgid "Continue" @@ -1892,6 +1905,7 @@ msgstr "Kopiuj token" #: apps/remix/app/routes/_profile+/_layout.tsx #: apps/remix/app/components/dialogs/webhook-create-dialog.tsx #: apps/remix/app/components/dialogs/organisation-group-create-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Create" msgstr "Utwórz" @@ -1928,6 +1942,14 @@ msgstr "Utwórz jako szkic" msgid "Create as pending" msgstr "Utwórz jako oczekujące" +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Create claim" +msgstr "" + +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Create Claim" +msgstr "" + #: apps/remix/app/components/dialogs/template-direct-link-dialog-wrapper.tsx msgid "Create Direct Link" msgstr "Utwórz bezpośredni link" @@ -1962,18 +1984,26 @@ msgstr "Utwórz jeden automatycznie" msgid "Create organisation" msgstr "" -#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx -msgid "Create Organisation" -msgstr "" - #: apps/remix/app/components/general/menu-switcher.tsx -msgid "Create Organization" +msgid "Create Organisation" msgstr "" #: apps/remix/app/components/dialogs/template-use-dialog.tsx msgid "Create signing links" msgstr "Utwórz linki do podpisania" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Create Stripe customer" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Create subscription" +msgstr "" + +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Create Subscription Claim" +msgstr "" + #: apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx @@ -2025,7 +2055,7 @@ msgid "Created" msgstr "Utworzono" #: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Created At" msgstr "Utworzono w" @@ -2034,7 +2064,6 @@ msgid "Created by" msgstr "Utworzono przez" #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx -#: apps/remix/app/components/tables/organisation-pending-teams-table.tsx msgid "Created on" msgstr "Utworzone w" @@ -2065,12 +2094,16 @@ msgstr "Aktualni odbiorcy:" msgid "Currently all organisation members can access this team" msgstr "" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups._index.tsx -msgid "Custom Organisation Groups" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx +msgid "Currently branding can only be configured for Teams and above plans." msgstr "" -#: apps/remix/app/components/general/billing-plans.tsx -msgid "Daily" +#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx +msgid "Currently branding can only be configured on the organisation level." +msgstr "" + +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups._index.tsx +msgid "Custom Organisation Groups" msgstr "" #: apps/remix/app/components/general/app-command-menu.tsx @@ -2130,16 +2163,20 @@ msgstr "usuń" #: apps/remix/app/components/tables/organisation-teams-table.tsx #: apps/remix/app/components/tables/organisation-groups-table.tsx #: apps/remix/app/components/tables/documents-table-action-dropdown.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx #: apps/remix/app/components/general/document/document-page-view-dropdown.tsx #: apps/remix/app/components/dialogs/webhook-delete-dialog.tsx #: apps/remix/app/components/dialogs/token-delete-dialog.tsx #: apps/remix/app/components/dialogs/template-delete-dialog.tsx #: apps/remix/app/components/dialogs/team-group-delete-dialog.tsx #: apps/remix/app/components/dialogs/team-delete-dialog.tsx +#: apps/remix/app/components/dialogs/team-delete-dialog.tsx #: apps/remix/app/components/dialogs/organisation-member-delete-dialog.tsx #: apps/remix/app/components/dialogs/organisation-group-delete-dialog.tsx #: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx +#: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx #: apps/remix/app/components/dialogs/document-delete-dialog.tsx +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx msgid "Delete" msgstr "Usuń" @@ -2179,7 +2216,6 @@ msgid "Delete Document" msgstr "Usuń dokument" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.general.tsx -#: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx msgid "Delete organisation" msgstr "" @@ -2195,8 +2231,11 @@ msgstr "" msgid "Delete passkey" msgstr "Usuń klucz dostępu" +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "Delete Subscription Claim" +msgstr "" + #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx -#: apps/remix/app/components/dialogs/team-delete-dialog.tsx msgid "Delete team" msgstr "Usuń zespół" @@ -2620,7 +2659,7 @@ msgstr "Wyświetlone dokumenty" msgid "Don't have an account? <0>Sign up" msgstr "Nie masz konta? <0>Zarejestruj się" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx #: apps/remix/app/components/tables/documents-table-action-dropdown.tsx #: apps/remix/app/components/tables/documents-table-action-button.tsx #: apps/remix/app/components/general/document/document-page-view-dropdown.tsx @@ -2675,7 +2714,7 @@ msgstr "Lista rozwijana" msgid "Dropdown options" msgstr "Opcje rozwijane" -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Due to an unpaid invoice, your team has been restricted. Please settle the payment to restore full access to your team." msgstr "Z powodu nieopłaconej faktury Twój zespół został ograniczony. Proszę uregulować płatność, aby przywrócić pełny dostęp do zespołu." @@ -2718,6 +2757,7 @@ msgstr "Ujawnienie podpisu elektronicznego" #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx #: apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-dashboard-users-table.tsx #: apps/remix/app/components/general/document-signing/document-signing-email-field.tsx @@ -2790,6 +2830,10 @@ msgstr "Weryfikacja e-mailu została usunięta" msgid "Email verification has been resent" msgstr "Weryfikacja e-mailu została ponownie wysłana" +#: packages/lib/types/subscription.ts +msgid "Embedding, 5 members included and more" +msgstr "" + #: packages/ui/primitives/document-flow/add-fields.tsx msgid "Empty field" msgstr "Puste pole" @@ -2820,7 +2864,7 @@ msgstr "Włącz konto" msgid "Enable Authenticator App" msgstr "Włącz aplikację uwierzytelniającą" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Enable custom branding for all documents in this team." msgstr "Włącz niestandardowe brandowanie dla wszystkich dokumentów w tym zespole." @@ -2849,11 +2893,11 @@ msgstr "Włączenie konta pozwala użytkownikowi na ponowne korzystanie z niego msgid "Enclosed Document" msgstr "Załączony dokument" -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx -msgid "Ends On" -msgstr "Kończy się" +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Enter claim name" +msgstr "" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Enter your brand details" msgstr "Wprowadź szczegóły swojej marki" @@ -2873,7 +2917,12 @@ msgstr "Wprowadź swoje imię" msgid "Enter your text here" msgstr "Wprowadź swój tekst tutaj" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Enterprise" +msgstr "" + #: apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx #: apps/remix/app/components/general/verify-email-banner.tsx #: apps/remix/app/components/general/template/template-edit-form.tsx @@ -2948,6 +2997,14 @@ msgstr "Wygasa {0}" msgid "External ID" msgstr "Zewnętrzny ID" +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Failed to create subscription claim." +msgstr "" + +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "Failed to delete subscription claim." +msgstr "" + #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx msgid "Failed to reseal document" msgstr "Nie udało się ponownie zaplombować dokumentu" @@ -2960,6 +3017,10 @@ msgstr "Nie udało się zapisać ustawień." msgid "Failed to update recipient" msgstr "Nie udało się zaktualizować odbiorcy" +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Failed to update subscription claim." +msgstr "" + #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.webhooks.$id.tsx msgid "Failed to update webhook" msgstr "Nie udało się zaktualizować webhooku" @@ -2968,6 +3029,12 @@ msgstr "Nie udało się zaktualizować webhooku" msgid "Failed: {failedCount}" msgstr "Niepowodzenia: {failedCount}" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Feature Flags" +msgstr "" + #: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx msgid "Field character limit" msgstr "Limit znaków pola" @@ -3019,6 +3086,10 @@ msgstr "Plik nie może mieć większej wielkości niż {APP_DOCUMENT_UPLOAD_SIZE msgid "File size exceeds the limit of {APP_DOCUMENT_UPLOAD_SIZE_LIMIT} MB" msgstr "Rozmiar pliku przekracza limit {APP_DOCUMENT_UPLOAD_SIZE_LIMIT} MB" +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Fill in the details to create a new subscription claim." +msgstr "" + #: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx #: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx #: packages/ui/primitives/document-flow/field-items-advanced-settings/name-field.tsx @@ -3036,6 +3107,10 @@ msgstr "W przypadku jakichkolwiek pytań dotyczących tego ujawnienia, podpisów msgid "For each recipient, provide their email (required) and name (optional) in separate columns. Download the template CSV below for the correct format." msgstr "Dla każdego odbiorcy podaj jego email (wymagany) i nazwę (opcjonalnie) w oddzielnych kolumnach. Pobierz poniżej szablon CSV dla właściwego formatu." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "For example, if the claim has a new flag \"FLAG_1\" set to true, then this organisation will get that flag added." +msgstr "" + #: packages/lib/server-only/auth/send-forgot-password.ts msgid "Forgot Password?" msgstr "Zapomniałeś hasła?" @@ -3046,6 +3121,10 @@ msgstr "Zapomniałeś hasła?" msgid "Forgot your password?" msgstr "Zapomniałeś swoje hasło?" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Free" +msgstr "" + #: packages/ui/primitives/document-flow/types.ts msgid "Free Signature" msgstr "Podpis wolny" @@ -3079,6 +3158,7 @@ msgid "Global recipient action authentication" msgstr "Globalne uwierzytelnianie akcji odbiorcy" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "Go back" msgstr "" @@ -3193,7 +3273,6 @@ msgid "Here you can manage your password and security settings." msgstr "Tutaj możesz zarządzać swoim hasłem i ustawieniami zabezpieczeń." #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx -#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx msgid "Here you can set preferences and defaults for branding." msgstr "Tutaj możesz ustawić preferencje i domyślne ustawienia dla brandowania." @@ -3260,6 +3339,14 @@ msgstr "Jestem właścicielem tego dokumentu" msgid "I'm sure! Delete it" msgstr "Jestem pewny! Usuń to" +#: apps/remix/app/components/tables/admin-claims-table.tsx +msgid "ID" +msgstr "" + +#: apps/remix/app/components/tables/admin-claims-table.tsx +msgid "ID copied to clipboard" +msgstr "" + #: apps/remix/app/components/dialogs/passkey-create-dialog.tsx msgid "If you do not want to use the authenticator prompted, you can close it, which will then display the next available authenticator." msgstr "Jeśli nie chcesz korzystać z proponowanego uwierzytelnienia, możesz je zamknąć, a następnie wyświetlić następne dostępne uwierzytelnienie." @@ -3298,6 +3385,7 @@ msgstr "Przechwyć metodę uwierzytelniania" #: apps/remix/app/components/forms/document-preferences-form.tsx #: apps/remix/app/components/forms/document-preferences-form.tsx #: apps/remix/app/components/forms/document-preferences-form.tsx +#: apps/remix/app/components/forms/document-preferences-form.tsx msgid "Inherit from organisation" msgstr "" @@ -3305,6 +3393,11 @@ msgstr "" msgid "Inherit organisation members" msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Inherited subscription claim" +msgstr "" + #: apps/remix/app/components/general/document-signing/document-signing-initials-field.tsx #: packages/ui/primitives/document-flow/types.ts msgid "Initials" @@ -3385,7 +3478,7 @@ msgstr "" msgid "Invited At" msgstr "Zaproś o" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx msgid "Invoice" msgstr "Faktura" @@ -3479,7 +3572,7 @@ msgstr "Ostatnie użycie" msgid "Leaderboard" msgstr "Ranking" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/dialogs/organisation-leave-dialog.tsx msgid "Leave" msgstr "Wyjdź" @@ -3541,8 +3634,10 @@ msgstr "Ładowanie..." msgid "Login" msgstr "Zaloguj się" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.preferences.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/tables/organisation-teams-table.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Manage" msgstr "Zarządzaj" @@ -3555,10 +3650,18 @@ msgstr "Zarządzaj profilem {0}" msgid "Manage all organisations you are currently associated with." msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/claims.tsx +msgid "Manage all subscription claims" +msgstr "" + #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/templates.$id._index.tsx msgid "Manage and view template" msgstr "Zarządzaj i przeglądaj szablon" +#: apps/remix/app/components/general/organisations/organisation-billing-portal-button.tsx +msgid "Manage billing" +msgstr "" + #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx msgid "Manage details for this public template" msgstr "Zarządzaj szczegółami tego publicznego szablonu" @@ -3571,6 +3674,14 @@ msgstr "Zarządzaj Bezpośrednim Linkiem" msgid "Manage documents" msgstr "Zarządzaj dokumentami" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Manage organisation" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx +msgid "Manage organisations" +msgstr "" + #: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx msgid "Manage passkeys" msgstr "Zarządzaj kluczami dostępu" @@ -3579,17 +3690,20 @@ msgstr "Zarządzaj kluczami dostępu" msgid "Manage permissions and access controls" msgstr "" -#: apps/remix/app/components/general/teams/team-billing-portal-button.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "Manage subscription" msgstr "Zarządzaj subskrypcją" -#: apps/remix/app/components/general/billing-portal-button.tsx -msgid "Manage Subscription" +#. placeholder {0}: organisation.name +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Manage the {0} organisation" msgstr "" -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx -msgid "Manage subscriptions" -msgstr "Zarządzaj subskrypcjami" +#. placeholder {1}: organisation.name +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Manage the {1} organisation subscription" +msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups._index.tsx msgid "Manage the custom groups of members for your organisation." @@ -3661,13 +3775,19 @@ msgstr "Max" msgid "Maximum file size: 4MB. Maximum 100 rows per upload. Blank values will use template defaults." msgstr "Maksymalny rozmiar pliku: 4MB. Maksymalnie 100 wierszy na przesyłkę. Puste wartości zostaną zastąpione domyślnymi z szablonu." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/dialogs/team-member-create-dialog.tsx #: packages/lib/constants/teams.ts #: packages/lib/constants/organisations.ts msgid "Member" msgstr "Członek" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Member Count" +msgstr "" + +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/tables/organisation-members-table.tsx msgid "Member Since" msgstr "Data dołączenia" @@ -3698,7 +3818,12 @@ msgstr "Min" msgid "Modify recipients" msgstr "Modyfikuj odbiorców" +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Modify the details of the subscription claim." +msgstr "" + #: apps/remix/app/components/general/billing-plans.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Monthly" msgstr "Miesięczny" @@ -3716,9 +3841,11 @@ msgstr "Miesięczni aktywni użytkownicy: Użytkownicy, którzy mieli przynajmni #: apps/remix/app/components/tables/admin-leaderboard-table.tsx #: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx #: apps/remix/app/components/tables/admin-dashboard-users-table.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx #: apps/remix/app/components/general/claim-account.tsx #: apps/remix/app/components/general/document-signing/document-signing-name-field.tsx #: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/template-use-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx @@ -3771,7 +3898,6 @@ msgstr "" msgid "New Template" msgstr "Nowy szablon" -#: apps/remix/app/components/forms/signup.tsx #: apps/remix/app/components/embed/embed-document-signing-page.tsx #: apps/remix/app/components/embed/embed-direct-template-client-page.tsx #: apps/remix/app/components/dialogs/team-member-create-dialog.tsx @@ -3846,6 +3972,10 @@ msgstr "Brak wyników." msgid "No signature field found" msgstr "Nie znaleziono pola podpisu" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "No Stripe customer attached" +msgstr "" + #: apps/remix/app/components/tables/team-groups-table.tsx msgid "No team groups found" msgstr "" @@ -3873,6 +4003,7 @@ msgstr "Nie znaleziono wartości." msgid "No worries, it happens! Enter your email and we'll email you a special link to reset your password." msgstr "Nie martw się, to się zdarza! Wprowadź swój e-mail, a my wyślemy Ci specjalny link do zresetowania hasła." +#: apps/remix/app/components/tables/admin-claims-table.tsx #: packages/lib/constants/document.ts msgid "None" msgstr "Brak" @@ -3897,6 +4028,16 @@ msgstr "Numer" msgid "Number format" msgstr "Format liczby" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Number of members allowed. 0 = Unlimited" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Number of teams allowed. 0 = Unlimited" +msgstr "" + #. placeholder {0}: document.team?.name #: apps/remix/app/components/general/document-signing/document-signing-page-view.tsx msgid "on behalf of \"{0}\" has invited you to approve this document" @@ -3957,10 +4098,6 @@ msgstr "Tylko administratorzy mogą uzyskać dostęp do dokumentu i go wyświetl msgid "Only managers and above can access and view the document" msgstr "Tylko menedżerowie i wyżej mogą uzyskać dostęp do dokumentu i go wyświetlić" -#: apps/remix/app/components/forms/signup.tsx -msgid "Only subscribers can have a username shorter than 6 characters" -msgstr "Tylko subskrybenci mogą mieć nazwę użytkownika krótszą niż 6 znaków" - #: apps/remix/app/routes/_profile+/_layout.tsx #: apps/remix/app/components/general/generic-error-layout.tsx #: apps/remix/app/components/general/generic-error-layout.tsx @@ -3980,7 +4117,8 @@ msgstr "Lub" msgid "Or continue with" msgstr "Lub kontynuuj z" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Organisation" msgstr "" @@ -3996,6 +4134,11 @@ msgstr "" msgid "Organisation Group Settings" msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Organisation has been updated successfully" +msgstr "" + #: apps/remix/app/routes/_unauthenticated+/organisation.invite.$token.tsx msgid "Organisation invitation" msgstr "" @@ -4015,15 +4158,18 @@ msgid "Organisation Member" msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.members.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "Organisation Members" msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/forms/organisation-update-form.tsx #: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Organisation Name" msgstr "" #: apps/remix/app/routes/_authenticated+/_layout.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx msgid "Organisation not found" msgstr "" @@ -4036,6 +4182,7 @@ msgid "Organisation role" msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/components/dialogs/organisation-member-invite-dialog.tsx msgid "Organisation Role" msgstr "" @@ -4047,13 +4194,18 @@ msgstr "" msgid "Organisation Settings" msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Organisation Teams" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/forms/organisation-update-form.tsx -#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Organisation URL" msgstr "" #: apps/remix/app/routes/_authenticated+/dashboard.tsx #: apps/remix/app/routes/_authenticated+/settings+/organisations.tsx +#: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx #: apps/remix/app/components/general/settings-nav-mobile.tsx #: apps/remix/app/components/general/settings-nav-desktop.tsx #: apps/remix/app/components/general/menu-switcher.tsx @@ -4076,8 +4228,9 @@ msgstr "W przeciwnym razie dokument zostanie utworzony jako wersja robocza." #: apps/remix/app/routes/_authenticated+/dashboard.tsx #: apps/remix/app/routes/_authenticated+/dashboard.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/tables/organisation-members-table.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Owner" msgstr "Właściciel" @@ -4092,10 +4245,6 @@ msgstr "Strona {0} z {1}" msgid "Page {0} of {numPages}" msgstr "Strona {0} z {numPages}" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx -msgid "Paid" -msgstr "Opłacono" - #: apps/remix/app/components/forms/signin.tsx msgid "Passkey" msgstr "Klucz dostępu" @@ -4166,20 +4315,11 @@ msgstr "Hasło zostało zaktualizowane" msgid "Password updated!" msgstr "Hasło zaktualizowane!" -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx -msgid "Pay" -msgstr "Zapłać" - -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "Payment is required to finalise the creation of your team." -msgstr "Płatność jest wymagana do zakończenia tworzenia zespołu." - -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Payment overdue" msgstr "Płatność zaległa" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.teams.tsx #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.members.tsx #: apps/remix/app/components/general/template/template-page-view-documents-table.tsx #: apps/remix/app/components/general/document/document-status.tsx @@ -4205,9 +4345,15 @@ msgstr "Oczekujące dokumenty" msgid "Pending invitations" msgstr "Oczekujące zaproszenia" -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx -msgid "Pending team deleted." -msgstr "Oczekujący zespół usunięty." +#: apps/remix/app/components/general/billing-plans.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "per month" +msgstr "" + +#: apps/remix/app/components/general/billing-plans.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "per year" +msgstr "" #: apps/remix/app/components/general/menu-switcher.tsx msgid "Personal Account" @@ -4369,6 +4515,10 @@ msgstr "Wpisz {0}, aby potwierdzić" msgid "Please type <0>{0} to confirm." msgstr "Wpisz <0>{0}, aby potwierdzić." +#: apps/remix/app/components/forms/branding-preferences-form.tsx +msgid "Please upload a logo" +msgstr "" + #: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx msgid "Pre-formatted CSV template with example data." msgstr "Wstępnie sformatowany szablon CSV z przykładowymi danymi." @@ -4435,10 +4585,6 @@ msgstr "Profil publiczny" msgid "Public profile URL" msgstr "Adres URL profilu publicznego" -#: apps/remix/app/components/forms/signup.tsx -msgid "Public profile username" -msgstr "Nazwa użytkownika profilu publicznego" - #: apps/remix/app/components/tables/templates-table.tsx msgid "Public templates are connected to your public profile. Any modifications to public templates will also appear in your public profile." msgstr "Szablony publiczne są powiązane z Twoim publicznym profilem. Wszelkie modyfikacje szablonów publicznych również pojawią się w Twoim publicznym profilu." @@ -4621,7 +4767,6 @@ msgstr "Przypomnienie: Proszę {recipientActionVerb} ten dokument" msgid "Reminder: Please {recipientActionVerb} your document" msgstr "Przypomnienie: Proszę {recipientActionVerb} Twój dokument" -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx #: apps/remix/app/components/tables/team-members-table.tsx #: apps/remix/app/components/tables/team-groups-table.tsx #: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx @@ -4630,7 +4775,7 @@ msgstr "Przypomnienie: Proszę {recipientActionVerb} Twój dokument" #: apps/remix/app/components/general/teams/team-email-dropdown.tsx #: apps/remix/app/components/general/document-signing/document-signing-field-container.tsx #: apps/remix/app/components/general/document-signing/document-signing-field-container.tsx -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx #: apps/remix/app/components/forms/avatar-image.tsx #: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx #: apps/remix/app/components/dialogs/template-bulk-send-dialog.tsx @@ -4710,11 +4855,11 @@ msgstr "Zresetuj hasło" msgid "Resetting Password..." msgstr "Resetowanie hasła..." -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Resolve" msgstr "Rozwiąż" -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Resolve payment" msgstr "Rozwiąż płatność" @@ -4754,7 +4899,7 @@ msgstr "Cofnij" msgid "Revoke access" msgstr "Cofnij dostęp" -#: apps/remix/app/components/tables/user-settings-organisations-table.tsx +#: apps/remix/app/components/tables/user-organisations-table.tsx #: apps/remix/app/components/tables/team-members-table.tsx #: apps/remix/app/components/tables/team-groups-table.tsx #: apps/remix/app/components/tables/organisation-members-table.tsx @@ -4764,7 +4909,6 @@ msgstr "Cofnij dostęp" #: apps/remix/app/components/dialogs/team-member-update-dialog.tsx #: apps/remix/app/components/dialogs/team-group-update-dialog.tsx #: apps/remix/app/components/dialogs/organisation-member-update-dialog.tsx -#: apps/remix/app/components/dialogs/organisation-member-invite-dialog.tsx msgid "Role" msgstr "Rola" @@ -4797,6 +4941,14 @@ msgstr "Zapisz szablon" msgid "Search" msgstr "Szukaj" +#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx +msgid "Search and manage all organisations" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/claims.tsx +msgid "Search by claim ID or name" +msgstr "" + #: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx msgid "Search by document title" msgstr "Szukaj tytułu dokumentu" @@ -4806,6 +4958,10 @@ msgstr "Szukaj tytułu dokumentu" msgid "Search by name or email" msgstr "Szukaj nazwy lub adresu e-mail" +#: apps/remix/app/routes/_authenticated+/admin+/organisations._index.tsx +msgid "Search by organisation ID, name, customer ID or owner email" +msgstr "" + #: apps/remix/app/components/general/document/document-search.tsx msgid "Search documents..." msgstr "Szukaj dokumentów..." @@ -4834,6 +4990,14 @@ msgstr "Aktywność bezpieczeństwa" msgid "Select" msgstr "Wybierz" +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Select a plan" +msgstr "" + +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx +msgid "Select a plan to continue" +msgstr "" + #: apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx msgid "Select a team to view its dashboard" msgstr "" @@ -4889,6 +5053,10 @@ msgstr "" msgid "Select passkey" msgstr "Wybierz klucz uwierzytelniający" +#: apps/remix/app/components/forms/document-preferences-form.tsx +msgid "Select signature types" +msgstr "" + #: apps/remix/app/components/dialogs/organisation-group-create-dialog.tsx msgid "Select the members to add to this group" msgstr "" @@ -5182,10 +5350,6 @@ msgstr "Podpisywanie" msgid "Signing Certificate" msgstr "Certyfikat podpisu" -#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx -msgid "Signing certificate provided by" -msgstr "Certyfikat podpisu dostarczony przez" - #: packages/lib/server-only/document/send-completed-email.ts #: packages/lib/server-only/document/send-completed-email.ts msgid "Signing Complete!" @@ -5243,24 +5407,23 @@ msgstr "Niektórzy sygnatariusze nie zostali przypisani do pola podpisu. Przypis #: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx #: apps/remix/app/routes/_unauthenticated+/verify-email.$token.tsx +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.public-profile.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.groups.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.groups.tsx -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/organisation-member-invites-table.tsx #: apps/remix/app/components/tables/organisation-member-invites-table.tsx #: apps/remix/app/components/tables/documents-table-action-dropdown.tsx #: apps/remix/app/components/tables/documents-table-action-button.tsx -#: apps/remix/app/components/general/billing-portal-button.tsx #: apps/remix/app/components/general/billing-plans.tsx -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx #: apps/remix/app/components/general/teams/team-email-usage.tsx #: apps/remix/app/components/general/teams/team-email-dropdown.tsx -#: apps/remix/app/components/general/teams/team-billing-portal-button.tsx #: apps/remix/app/components/general/organisations/organisation-invitations.tsx #: apps/remix/app/components/general/organisations/organisation-invitations.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-portal-button.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx #: apps/remix/app/components/general/document-signing/document-signing-auth-page.tsx #: apps/remix/app/components/general/document-signing/document-signing-auth-account.tsx #: apps/remix/app/components/general/document/document-page-view-dropdown.tsx @@ -5269,7 +5432,6 @@ msgstr "Niektórzy sygnatariusze nie zostali przypisani do pola podpisu. Przypis #: apps/remix/app/components/general/document/document-audit-log-download-button.tsx #: apps/remix/app/components/general/direct-template/direct-template-signing-auth-page.tsx #: apps/remix/app/components/general/direct-template/direct-template-page.tsx -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx #: apps/remix/app/components/embed/embed-document-signing-page.tsx #: apps/remix/app/components/embed/embed-direct-template-client-page.tsx #: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx @@ -5279,8 +5441,6 @@ msgstr "Niektórzy sygnatariusze nie zostali przypisani do pola podpisu. Przypis #: apps/remix/app/components/dialogs/template-create-dialog.tsx #: apps/remix/app/components/dialogs/team-email-delete-dialog.tsx #: apps/remix/app/components/dialogs/team-email-delete-dialog.tsx -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx #: apps/remix/app/components/dialogs/organisation-member-invite-dialog.tsx #: apps/remix/app/components/dialogs/document-resend-dialog.tsx #: apps/remix/app/components/dialogs/document-duplicate-dialog.tsx @@ -5347,9 +5507,8 @@ msgid "Stats" msgstr "Statystyki" #: apps/remix/app/routes/_internal+/[__htmltopdf]+/audit-log.tsx -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx #: apps/remix/app/components/tables/documents-table.tsx #: apps/remix/app/components/general/template/template-page-view-documents-table.tsx msgid "Status" @@ -5359,6 +5518,18 @@ msgstr "Stan" msgid "Step <0>{step} of {maxStep}" msgstr "Krok <0>{step} z {maxStep}" +#: apps/remix/app/components/tables/admin-organisations-table.tsx +msgid "Stripe" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Stripe customer created successfully" +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Stripe Customer ID" +msgstr "" + #: packages/ui/primitives/template-flow/add-template-settings.tsx #: packages/ui/primitives/document-flow/add-subject.tsx msgid "Subject <0>(Optional)" @@ -5368,17 +5539,32 @@ msgstr "Temat <0>(Opcjonalnie)" msgid "Subscribe" msgstr "" -#: apps/remix/app/components/tables/admin-dashboard-users-table.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/tables/admin-organisations-table.tsx msgid "Subscription" msgstr "Subskrypcja" -#: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx -msgid "Subscriptions" -msgstr "Subskrypcje" +#: apps/remix/app/components/dialogs/claim-create-dialog.tsx +msgid "Subscription claim created successfully." +msgstr "" + +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "Subscription claim deleted successfully." +msgstr "" + +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Subscription claim updated successfully." +msgstr "" + +#: apps/remix/app/routes/_authenticated+/admin+/claims.tsx +msgid "Subscription Claims" +msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/organisation-member-invites-table.tsx @@ -5440,8 +5626,8 @@ msgid "System Theme" msgstr "Motyw systemowy" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/tables/organisation-teams-table.tsx -#: apps/remix/app/components/tables/organisation-pending-teams-table.tsx msgid "Team" msgstr "Zespół" @@ -5458,9 +5644,10 @@ msgstr "" msgid "Team Assignments" msgstr "" -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "Team checkout" -msgstr "Zakupy zespołowe" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/components/forms/subscription-claim-form.tsx +msgid "Team Count" +msgstr "" #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx #: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings._index.tsx @@ -5570,6 +5757,10 @@ msgstr "Ustawienia zespołu" msgid "Team templates" msgstr "Szablony zespołu" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "Team url" +msgstr "" + #: apps/remix/app/components/forms/team-update-form.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx msgid "Team URL" @@ -5587,7 +5778,7 @@ msgstr "Zespoły" msgid "Teams help you organize your work and collaborate with others. Create your first team to get started." msgstr "" -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Teams restricted" msgstr "Zespoły ograniczone" @@ -5764,6 +5955,12 @@ msgstr "" msgid "The organisation role that will be applied to all members in this group." msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "" +"The organisation you are looking for may have been removed, renamed or may have never\n" +" existed." +msgstr "" + #: apps/remix/app/routes/_authenticated+/_layout.tsx msgid "" "The organisation you are looking for may have been removed, renamed or may have never\n" @@ -5936,6 +6133,10 @@ msgstr "Ta akcja jest odwracalna, ale proszę być ostrożnym, ponieważ konto m msgid "This can be overriden by setting the authentication requirements directly on each recipient in the next step." msgstr "To można nadpisać, ustawiając wymagania dotyczące uwierzytelniania bezpośrednio na każdym odbiorcy w następnym kroku." +#: apps/remix/app/components/dialogs/claim-delete-dialog.tsx +msgid "This claim is locked and cannot be deleted." +msgstr "" + #: packages/email/template-components/template-document-super-delete.tsx msgid "This document can not be recovered, if you would like to dispute the reason for future documents please contact support." msgstr "Dokument ten nie może być odzyskany. Jeśli chcesz zakwestionować przyczynę przyszłych dokumentów, skontaktuj się z administracją." @@ -6020,6 +6221,10 @@ msgstr "To pole nie może być modyfikowane ani usuwane. Po udostępnieniu bezpo msgid "This is how the document will reach the recipients once the document is ready for signing." msgstr "W ten sposób dokument dotrze do odbiorców, gdy tylko dokument będzie gotowy do podpisania." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "This is the claim that this organisation was initially created with. Any feature flag changes to this claim will be backported into this organisation." +msgstr "" + #: apps/remix/app/routes/_unauthenticated+/team.verify.transfer.token.tsx msgid "This link is invalid or has expired." msgstr "" @@ -6040,10 +6245,6 @@ msgstr "Ten klucz dostępu został już zarejestrowany." msgid "This passkey is not configured for this application. Please login and add one in the user settings." msgstr "Ten klucz dostępu nie jest skonfigurowany dla tej aplikacji. Proszę zalogować się i dodać jeden w ustawieniach użytkownika." -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "This price includes minimum 5 seats." -msgstr "Ta cena obejmuje co najmniej 5 miejsc." - #: packages/ui/primitives/document-flow/add-fields.tsx msgid "This recipient can no longer be modified as they have signed a field, or completed the document." msgstr "Ten odbiorca nie może być już modyfikowany, ponieważ podpisał pole lub ukończył dokument." @@ -6075,14 +6276,9 @@ msgstr "Ten token jest nieprawidłowy lub wygasł. Proszę skontaktować się ze #: apps/remix/app/components/forms/team-update-form.tsx #: apps/remix/app/components/forms/organisation-update-form.tsx #: apps/remix/app/components/dialogs/team-create-dialog.tsx -#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "This URL is already in use." msgstr "Ten URL jest już używany." -#: apps/remix/app/components/forms/signup.tsx -msgid "This username has already been taken" -msgstr "Ta nazwa użytkownika została już zajęta" - #: packages/ui/components/document/document-email-checkboxes.tsx msgid "This will be sent to all recipients if a pending document has been deleted." msgstr "To zostanie wysłane do wszystkich odbiorców, jeśli oczekujący dokument został usunięty." @@ -6095,6 +6291,10 @@ msgstr "To zostanie wysłane do wszystkich odbiorców, gdy dokument będzie cał msgid "This will be sent to the document owner once the document has been fully completed." msgstr "To zostanie wysłane do właściciela dokumentu, gdy dokument zostanie w pełni ukończony." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "This will ONLY backport feature flags which are set to true, anything disabled in the initial claim will not be backported" +msgstr "" + #: packages/ui/components/recipient/recipient-action-auth-select.tsx msgid "This will override any global settings." msgstr "To zastąpi wszystkie globalne ustawienia." @@ -6385,20 +6585,27 @@ msgstr "Niezakończony" msgid "Unknown" msgstr "Nieznany" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx -msgid "Unpaid" -msgstr "Nieopłacone" +#: apps/remix/app/components/tables/admin-claims-table.tsx +msgid "Unlimited" +msgstr "" + +#: packages/lib/types/subscription.ts +msgid "Unlimited documents, API and more" +msgstr "" #: apps/remix/app/components/dialogs/team-group-create-dialog.tsx msgid "Untitled Group" msgstr "" #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx #: apps/remix/app/components/tables/settings-security-passkey-table-actions.tsx #: apps/remix/app/components/tables/settings-public-profile-templates-table.tsx -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/tables/admin-claims-table.tsx #: apps/remix/app/components/forms/public-profile-form.tsx #: apps/remix/app/components/forms/document-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx #: apps/remix/app/components/dialogs/team-member-update-dialog.tsx #: apps/remix/app/components/dialogs/team-group-update-dialog.tsx #: apps/remix/app/components/dialogs/team-email-update-dialog.tsx @@ -6413,6 +6620,14 @@ msgstr "Zaktualizuj" msgid "Update Banner" msgstr "Zaktualizuj baner" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx +msgid "Update Billing" +msgstr "" + +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Update Claim" +msgstr "" + #: apps/remix/app/components/forms/organisation-update-form.tsx msgid "Update organisation" msgstr "" @@ -6445,6 +6660,10 @@ msgstr "Zaktualizuj odbiorcę" msgid "Update role" msgstr "Zaktualizuj rolę" +#: apps/remix/app/components/dialogs/claim-update-dialog.tsx +msgid "Update Subscription Claim" +msgstr "" + #: apps/remix/app/components/forms/team-update-form.tsx msgid "Update team" msgstr "Zaktualizuj zespół" @@ -6524,7 +6743,7 @@ msgstr "Prześlij podpis" msgid "Upload Template Document" msgstr "Prześlij dokument szablonu" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Upload your brand logo (max 5MB, JPG, PNG, or WebP)" msgstr "Prześlij logo swojej marki (maks. 5MB, JPG, PNG lub WebP)" @@ -6572,10 +6791,6 @@ msgstr "Użytkownik" msgid "User has no password." msgstr "Użytkownik nie ma hasła." -#: apps/remix/app/routes/_authenticated+/admin+/subscriptions.tsx -msgid "User ID" -msgstr "ID użytkownika" - #: apps/remix/app/components/dialogs/admin-user-enable-dialog.tsx #: apps/remix/app/components/dialogs/admin-user-disable-dialog.tsx #: apps/remix/app/components/dialogs/admin-user-delete-dialog.tsx @@ -6590,10 +6805,6 @@ msgstr "Profile użytkowników są tutaj!" msgid "User with this email already exists. Please use a different email address." msgstr "Użytkownik z tym adresem e-mail już istnieje. Proszę użyć innego adresu e-mail." -#: apps/remix/app/components/forms/signup.tsx -msgid "Username can only container alphanumeric characters and dashes." -msgstr "Nazwa użytkownika może zawierać tylko znaki alfanumeryczne i myślniki." - #: apps/remix/app/routes/_authenticated+/admin+/_layout.tsx msgid "Users" msgstr "Użytkownicy" @@ -6641,7 +6852,7 @@ msgstr "Zweryfikuj swój adres e-mail zespołu" msgid "Version History" msgstr "Historia wersji" -#: apps/remix/app/components/tables/team-settings-billing-invoices-table.tsx +#: apps/remix/app/components/tables/organisation-billing-invoices-table.tsx #: apps/remix/app/components/tables/inbox-table.tsx #: apps/remix/app/components/tables/documents-table-action-dropdown.tsx #: apps/remix/app/components/tables/documents-table-action-button.tsx @@ -6709,6 +6920,10 @@ msgstr "Zobacz więcej" msgid "View Original Document" msgstr "Wyświetl oryginalny dokument" +#: apps/remix/app/components/tables/admin-organisations-table.tsx +msgid "View owner" +msgstr "" + #: packages/email/template-components/template-document-self-signed.tsx msgid "View plans" msgstr "Zobacz plany" @@ -6778,9 +6993,8 @@ msgstr "Chcesz mieć profil publiczny?" msgid "Warning: Assistant as last signer" msgstr "" -#: apps/remix/app/components/general/billing-portal-button.tsx -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx -#: apps/remix/app/components/general/teams/team-billing-portal-button.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-portal-button.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "We are unable to proceed to the billing portal at this time. Please try again, or contact support." msgstr "Nie możemy przejść do portalu rozliczeń w tej chwili. Proszę spróbuj ponownie lub skontaktuj się z pomocą techniczną." @@ -6792,10 +7006,19 @@ msgstr "Nie możemy usunąć tego klucza zabezpieczeń w tej chwili. Proszę spr msgid "We are unable to update this passkey at the moment. Please try again later." msgstr "Nie możemy zaktualizować tego klucza zabezpieczeń w tej chwili. Proszę spróbuj ponownie później." +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "We couldn't create a Stripe customer. Please try again." +msgstr "" + #: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.groups.$id.tsx msgid "We couldn't update the group. Please try again." msgstr "" +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx +msgid "We couldn't update the organisation. Please try again." +msgstr "" + #: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx msgid "We encountered an error while removing the direct template link. Please try again later." msgstr "Wystąpił błąd podczas usuwania bezpośredniego linku do szablonu. Proszę spróbować ponownie później." @@ -6829,10 +7052,6 @@ msgstr "Natknęliśmy się na nieznany błąd podczas próby utworzenia zespołu msgid "We encountered an unknown error while attempting to delete it. Please try again later." msgstr "Natknęliśmy się na nieznany błąd podczas próby usunięcia tego. Proszę spróbuj ponownie później." -#: apps/remix/app/components/tables/user-settings-pending-teams-table-actions.tsx -msgid "We encountered an unknown error while attempting to delete the pending team. Please try again later." -msgstr "Natknęliśmy się na nieznany błąd podczas próby usunięcia oczekującego zespołu. Proszę spróbuj ponownie później." - #: apps/remix/app/components/dialogs/organisation-delete-dialog.tsx msgid "We encountered an unknown error while attempting to delete this organisation. Please try again later." msgstr "" @@ -6943,10 +7162,6 @@ msgstr "Napotkaliśmy nieznany błąd podczas próby aktualizacji Twojego profil msgid "We have sent a confirmation email for verification." msgstr "Wysłaliśmy wiadomość e-mail z potwierdzeniem dla weryfikacji." -#: apps/remix/app/components/forms/signup.tsx -msgid "We need a username to create your profile" -msgstr "Potrzebujemy nazwy użytkownika, aby utworzyć Twój profil" - #: apps/remix/app/components/forms/signup.tsx msgid "We need your signature to sign documents" msgstr "Potrzebujemy Twojego podpisu, aby podpisać dokumenty" @@ -6959,10 +7174,6 @@ msgstr "Nie udało nam się skopiować tokena do schowka. Spróbuj ponownie." msgid "We were unable to copy your recovery code to your clipboard. Please try again." msgstr "Nie udało nam się skopiować twojego kodu odzyskiwania do schowka. Spróbuj ponownie." -#: apps/remix/app/components/dialogs/team-checkout-create-dialog.tsx -msgid "We were unable to create a checkout session. Please try again, or contact support" -msgstr "Nie udało się utworzyć sesji zakupu. Proszę spróbuj ponownie lub skontaktuj się z pomocą techniczną" - #: apps/remix/app/components/forms/signup.tsx msgid "We were unable to create your account. Please review the information you provided and try again." msgstr "Nie udało się utworzyć Twojego konta. Proszę sprawdzić podane informacje i spróbować ponownie." @@ -6992,7 +7203,7 @@ msgstr "Nie udało nam się skonfigurować uwierzytelniania dwuskładnikowego dl msgid "We were unable to submit this document at this time. Please try again later." msgstr "Nie udało nam się złożyć tego dokumentu w tej chwili. Proszę spróbuj ponownie później." -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx msgid "We were unable to update your branding preferences at this time, please try again later" msgstr "Nie udało nam się zaktualizować ustawień dotyczących marki w tym czasie, spróbuj ponownie później" @@ -7065,10 +7276,6 @@ msgstr "URL webhooka" msgid "Webhooks" msgstr "Webhooki" -#: apps/remix/app/components/general/billing-plans.tsx -msgid "Weekly" -msgstr "" - #: apps/remix/app/routes/_unauthenticated+/articles.signature-disclosure.tsx msgid "Welcome" msgstr "Witaj" @@ -7114,6 +7321,10 @@ msgstr "Kiedy korzystasz z naszej platformy, aby przyczepić swój podpis elektr msgid "While waiting for them to do so you can create your own Documenso account and get started with document signing right away." msgstr "Czekając na ich działania możesz utworzyć własne konto Documenso i od razu rozpocząć podpisywanie dokumentów." +#: packages/lib/types/subscription.ts +msgid "Whitelabeling, unlimited members and more" +msgstr "" + #: apps/remix/app/components/dialogs/document-resend-dialog.tsx msgid "Who do you want to remind?" msgstr "Kogo chcesz przypomnieć?" @@ -7131,6 +7342,7 @@ msgid "Write about yourself" msgstr "Napisz o sobie" #: apps/remix/app/components/general/billing-plans.tsx +#: apps/remix/app/components/dialogs/organisation-create-dialog.tsx msgid "Yearly" msgstr "Rocznie" @@ -7200,6 +7412,10 @@ msgstr "Zaraz cofniesz dostęp dla zespołu <0>{0} ({1}) do korzystania z tw msgid "You are about to send this document to the recipients. Are you sure you want to continue?" msgstr "Zaraz wyślesz ten dokument do odbiorców. Czy na pewno chcesz kontynuować?" +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "You are currently on the <0>Free Plan." +msgstr "" + #: apps/remix/app/components/dialogs/team-member-update-dialog.tsx msgid "You are currently updating <0>{memberName}." msgstr "" @@ -7306,12 +7522,12 @@ msgstr "Nie możesz przesyłać dokumentów w tej chwili." msgid "You cannot upload encrypted PDFs" msgstr "Nie możesz przesyłać zaszyfrowanych plików PDF" -#: apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx -msgid "You currently have no access to any teams within this organisation. Please contact your organisation to request access." +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "You currently have an inactive <0>{currentProductName} subscription" msgstr "" -#: apps/remix/app/components/general/billing-portal-button.tsx -msgid "You do not currently have a customer record, this should not happen. Please contact support for assistance." +#: apps/remix/app/routes/_authenticated+/org.$orgUrl._index.tsx +msgid "You currently have no access to any teams within this organisation. Please contact your organisation to request access." msgstr "" #: apps/remix/app/components/forms/token.tsx @@ -7346,10 +7562,6 @@ msgstr "Zostałeś zaproszony do dołączenia do {0} na Documenso" msgid "You have been invited to join the following organisation" msgstr "" -#: packages/email/templates/organisation-invite.tsx -msgid "You have been invited to join the following team" -msgstr "Zostałeś zaproszony do dołączenia do następującego zespołu" - #: packages/lib/server-only/recipient/set-document-recipients.ts #: packages/lib/server-only/recipient/delete-document-recipient.ts msgid "You have been removed from a document" @@ -7383,6 +7595,10 @@ msgstr "Brak utworzonych lub odebranych dokumentów. Prześlij, aby utworzyć." msgid "You have reached the maximum limit of {0} direct templates. <0>Upgrade your account to continue!" msgstr "Osiągnąłeś maksymalny limit {0} bezpośrednich szablonów. <0>Ulepsz swoje konto, aby kontynuować!" +#: apps/remix/app/components/dialogs/team-create-dialog.tsx +msgid "You have reached the maximum number of teams for your plan. Please contact sales at <0>support@documenso.com if you would like to adjust your plan." +msgstr "" + #: apps/remix/app/components/general/document/document-upload.tsx msgid "You have reached your document limit for this month. Please upgrade your plan." msgstr "Osiągnąłeś limit dokumentów na ten miesiąc. Proszę zaktualizować swój plan." @@ -7482,10 +7698,6 @@ msgstr "Musisz być zalogowany, aby zobaczyć tę stronę." msgid "You need to setup 2FA to mark this document as viewed." msgstr "Musisz skonfigurować 2FA, aby oznaczyć ten dokument jako przeczytany." -#: apps/remix/app/components/forms/signup.tsx -msgid "You will get notified & be able to set up your documenso public profile when we launch the feature." -msgstr "Otrzymasz powiadomienie i będziesz mógł skonfigurować swój publiczny profil documenso, gdy uruchomimy tę funkcję." - #: apps/remix/app/components/forms/2fa/enable-authenticator-app-dialog.tsx msgid "You will now be required to enter a code from your authenticator app when signing in." msgstr "Będziesz teraz zobowiązany do wpisania kodu z aplikacji uwierzytelniającej podczas logowania." @@ -7506,11 +7718,11 @@ msgstr "Awatar został zaktualizowany." msgid "Your banner has been updated successfully." msgstr "Twój banner został pomyślnie zaktualizowany." -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/components/forms/branding-preferences-form.tsx msgid "Your brand website URL" msgstr "Adres URL witryny Twojej marki" -#: apps/remix/app/components/forms/team-branding-preferences-form.tsx +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.preferences.tsx msgid "Your branding preferences have been updated" msgstr "Preferencje dotyczące marki zostały zaktualizowane" @@ -7522,6 +7734,18 @@ msgstr "Twoja masowa wysyłka została zainicjowana. Otrzymasz powiadomienie e-m msgid "Your bulk send operation for template \"{templateName}\" has completed." msgstr "Twoja operacja masowej wysyłki dla szablonu \"{templateName}\" została zakończona." +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "Your current {currentProductName} plan is past due. Please update your payment information." +msgstr "" + +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "Your current plan is inactive." +msgstr "" + +#: apps/remix/app/routes/_authenticated+/org.$orgUrl.settings.billing.tsx +msgid "Your current plan is past due." +msgstr "" + #: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx msgid "Your direct signing templates" msgstr "Twoje bezpośrednie szablony podpisu" @@ -7611,7 +7835,7 @@ msgstr "Hasło zostało zaktualizowane." msgid "Your password has been updated." msgstr "Hasło zostało zaktualizowane." -#: apps/remix/app/components/general/teams/team-layout-billing-banner.tsx +#: apps/remix/app/components/general/organisations/organisation-billing-banner.tsx msgid "Your payment for teams is overdue. Please settle the payment to avoid any service disruptions." msgstr "Twoja płatność za zespoły jest przeterminowana. Proszę uregulować płatność, aby uniknąć zakłóceń w świadczeniu usług." diff --git a/packages/lib/types/organisation.ts b/packages/lib/types/organisation.ts index 891232b22..d581accb2 100644 --- a/packages/lib/types/organisation.ts +++ b/packages/lib/types/organisation.ts @@ -1,12 +1,8 @@ import type { z } from 'zod'; +import OrganisationClaimSchema from '@documenso/prisma/generated/zod/modelSchema/OrganisationClaimSchema'; import { OrganisationSchema } from '@documenso/prisma/generated/zod/modelSchema/OrganisationSchema'; -/** - * The full document response schema. - * - * Mainly used for returning a single document from the API. - */ export const ZOrganisationSchema = OrganisationSchema.pick({ id: true, createdAt: true, @@ -17,54 +13,20 @@ export const ZOrganisationSchema = OrganisationSchema.pick({ customerId: true, ownerUserId: true, }).extend({ - // // Todo: Maybe we want to alter this a bit since this returns a lot of data. - // documentData: OrganisationDataSchema.pick({ - // type: true, - // id: true, - // data: true, - // initialData: true, - // }), - // documentMeta: OrganisationMetaSchema.pick({ - // signingOrder: true, - // distributionMethod: true, - // id: true, - // subject: true, - // message: true, - // timezone: true, - // password: true, - // dateFormat: true, - // documentId: true, - // redirectUrl: true, - // typedSignatureEnabled: true, - // uploadSignatureEnabled: true, - // drawSignatureEnabled: true, - // allowDictateNextSigner: true, - // language: true, - // emailSettings: true, - // }).nullable(), - // recipients: ZRecipientLiteSchema.array(), - // fields: ZFieldSchema.array(), + organisationClaim: OrganisationClaimSchema.pick({ + id: true, + createdAt: true, + updatedAt: true, + originalSubscriptionClaimId: true, + teamCount: true, + memberCount: true, + flags: true, + }), }); export type TOrganisation = z.infer; -/** - * A lite version of the document response schema without relations. - */ export const ZOrganisationLiteSchema = OrganisationSchema.pick({ - id: true, - createdAt: true, - updatedAt: true, - name: true, - avatarImageId: true, - customerId: true, - ownerUserId: true, -}); - -/** - * A version of the document response schema when returning multiple documents at once from a single API endpoint. - */ -export const ZOrganisationManySchema = OrganisationSchema.pick({ id: true, createdAt: true, updatedAt: true, @@ -73,15 +35,9 @@ export const ZOrganisationManySchema = OrganisationSchema.pick({ avatarImageId: true, customerId: true, ownerUserId: true, -}).extend({ - // user: UserSchema.pick({ - // id: true, - // name: true, - // email: true, - // }), - // recipients: ZRecipientLiteSchema.array(), - // team: TeamSchema.pick({ - // id: true, - // url: true, - // }).nullable(), }); + +/** + * A version of the organisation response schema when returning multiple organisations at once from a single API endpoint. + */ +export const ZOrganisationManySchema = ZOrganisationLiteSchema; diff --git a/packages/lib/types/subscription.ts b/packages/lib/types/subscription.ts new file mode 100644 index 000000000..3cfa87e8c --- /dev/null +++ b/packages/lib/types/subscription.ts @@ -0,0 +1,177 @@ +import type { MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; +import type { SubscriptionClaim } from '@prisma/client'; +import { z } from 'zod'; + +import { ZOrganisationNameSchema } from '@documenso/trpc/server/organisation-router/create-organisation.types'; + +export const ZClaimFlagsSchema = z.object({ + unlimitedDocuments: z.boolean().optional(), + + /** + * Allows disabling of Documenso branding for: + * - Certificates + * - Emails + * - Todo: orgs + * + * Rename to allowCustomBranding + */ + branding: z.boolean().optional(), + + embedAuthoring: z.boolean().optional(), + embedAuthoringWhiteLabel: z.boolean().optional(), + + embedSigning: z.boolean().optional(), + embedSigningWhiteLabel: z.boolean().optional(), + + cfr21: z.boolean().optional(), +}); + +export type TClaimFlags = z.infer; + +// When adding keys, update internal documentation with this. +export const SUBSCRIPTION_CLAIM_FEATURE_FLAGS: Record< + keyof TClaimFlags, + { + label: string; + key: keyof TClaimFlags; + } +> = { + unlimitedDocuments: { + key: 'unlimitedDocuments', + label: 'Unlimited documents', + }, + branding: { + key: 'branding', + label: 'Branding', + }, + embedAuthoring: { + key: 'embedAuthoring', + label: 'Embed authoring', + }, + embedSigning: { + key: 'embedSigning', + label: 'Embed signing', + }, + embedAuthoringWhiteLabel: { + key: 'embedAuthoringWhiteLabel', + label: 'White label for embed authoring', + }, + embedSigningWhiteLabel: { + key: 'embedSigningWhiteLabel', + label: 'White label for embed signing', + }, + cfr21: { + key: 'cfr21', + label: '21 CFR', + }, +}; + +export enum INTERNAL_CLAIM_ID { + FREE = 'free', + INDIVIDUAL = 'individual', + PRO = 'pro', + EARLY_ADOPTER = 'earlyAdopter', + PLATFORM = 'platform', + ENTERPRISE = 'enterprise', +} + +export type InternalClaim = Omit & { + description: MessageDescriptor | string; +}; + +export type InternalClaims = { + [key in INTERNAL_CLAIM_ID]: InternalClaim; +}; + +export const internalClaims: InternalClaims = { + [INTERNAL_CLAIM_ID.FREE]: { + id: INTERNAL_CLAIM_ID.FREE, + name: 'Free', + description: msg`5 Documents a month`, + teamCount: 1, + memberCount: 1, + locked: true, + flags: {}, + }, + [INTERNAL_CLAIM_ID.INDIVIDUAL]: { + id: INTERNAL_CLAIM_ID.INDIVIDUAL, + name: 'Individual', + description: msg`Unlimited documents, API and more`, + teamCount: 1, + memberCount: 1, + locked: true, + flags: { + unlimitedDocuments: true, + }, + }, + [INTERNAL_CLAIM_ID.PRO]: { + id: INTERNAL_CLAIM_ID.PRO, // Team -> Pro + name: 'Teams', + description: msg`Embedding, 5 members included and more`, + teamCount: 1, + memberCount: 5, + locked: true, + flags: { + unlimitedDocuments: true, + branding: true, + embedSigning: true, // Pro (team) plan only gets embedSigning right? + }, + }, + [INTERNAL_CLAIM_ID.PLATFORM]: { + id: INTERNAL_CLAIM_ID.PLATFORM, + name: 'Platform', + description: msg`Whitelabeling, unlimited members and more`, + teamCount: 1, + memberCount: 0, + locked: true, + flags: { + unlimitedDocuments: true, + branding: true, + embedAuthoring: false, + embedAuthoringWhiteLabel: true, + embedSigning: false, + embedSigningWhiteLabel: true, + }, + }, + [INTERNAL_CLAIM_ID.ENTERPRISE]: { + id: INTERNAL_CLAIM_ID.ENTERPRISE, + name: 'Enterprise', + description: '', + teamCount: 0, + memberCount: 0, + locked: true, + flags: { + unlimitedDocuments: true, + branding: true, + embedAuthoring: true, + embedAuthoringWhiteLabel: true, + embedSigning: true, + embedSigningWhiteLabel: true, + cfr21: true, + }, + }, + [INTERNAL_CLAIM_ID.EARLY_ADOPTER]: { + id: INTERNAL_CLAIM_ID.EARLY_ADOPTER, + name: 'Early Adopter', + description: '', + teamCount: 0, + memberCount: 0, + locked: true, + flags: { + unlimitedDocuments: true, + branding: true, + embedSigning: true, + embedSigningWhiteLabel: true, + }, + }, +} as const; + +export const ZStripeOrganisationCreateMetadataSchema = z.object({ + organisationName: ZOrganisationNameSchema, + userId: z.number(), +}); + +export type StripeOrganisationCreateMetadata = z.infer< + typeof ZStripeOrganisationCreateMetadataSchema +>; diff --git a/packages/lib/universal/id.ts b/packages/lib/universal/id.ts index 0d40dd088..3bb07d356 100644 --- a/packages/lib/universal/id.ts +++ b/packages/lib/universal/id.ts @@ -3,3 +3,7 @@ import { customAlphabet } from 'nanoid'; export const alphaid = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 21); export { nanoid } from 'nanoid'; + +export const generatePrefixedId = (prefix: string, length = 8) => { + return `${prefix}_${alphaid(length)}`; +}; diff --git a/packages/lib/utils/billing.ts b/packages/lib/utils/billing.ts index eab117882..b11b7af3b 100644 --- a/packages/lib/utils/billing.ts +++ b/packages/lib/utils/billing.ts @@ -1,36 +1,39 @@ -import type { Subscription } from '@prisma/client'; -import { SubscriptionStatus } from '@prisma/client'; +import type { Subscription } from '@documenso/prisma/generated/zod/modelSchema/SubscriptionSchema'; + +import { IS_BILLING_ENABLED } from '../constants/app'; +import { AppErrorCode } from '../errors/app-error'; +import { AppError } from '../errors/app-error'; +import type { StripeOrganisationCreateMetadata } from '../types/subscription'; + +export const generateStripeOrganisationCreateMetadata = ( + organisationName: string, + userId: number, +) => { + const metadata: StripeOrganisationCreateMetadata = { + organisationName, + userId, + }; + + return { + organisationCreateData: JSON.stringify(metadata), + }; +}; /** - * Returns true if there is a subscription that is active and is one of the provided price IDs. + * Throws an error if billing is enabled and no subscription is found. */ -export const subscriptionsContainsActivePlan = ( - subscriptions: Subscription[], - priceIds: string[], - allowPastDue?: boolean, -) => { - const allowedSubscriptionStatuses: SubscriptionStatus[] = [SubscriptionStatus.ACTIVE]; +export const validateIfSubscriptionIsRequired = (subscription?: Subscription | null) => { + const isBillingEnabled = IS_BILLING_ENABLED(); - if (allowPastDue) { - allowedSubscriptionStatuses.push(SubscriptionStatus.PAST_DUE); + if (!isBillingEnabled) { + return; } - return subscriptions.some( - (subscription) => - allowedSubscriptionStatuses.includes(subscription.status) && - priceIds.includes(subscription.priceId), - ); -}; + if (isBillingEnabled && !subscription) { + throw new AppError(AppErrorCode.NOT_FOUND, { + message: 'Subscription not found', + }); + } -/** - * Returns true if there is a subscription that is active and is one of the provided product IDs. - */ -export const subscriptionsContainsActiveProductId = ( - subscriptions: Subscription[], - productId: string[], -) => { - return subscriptions.some( - (subscription) => - subscription.status === SubscriptionStatus.ACTIVE && productId.includes(subscription.planId), - ); + return subscription; }; diff --git a/packages/lib/utils/i18n.ts b/packages/lib/utils/i18n.ts index b2dd7e4f6..0e9305cac 100644 --- a/packages/lib/utils/i18n.ts +++ b/packages/lib/utils/i18n.ts @@ -1,5 +1,6 @@ import type { I18n, MessageDescriptor } from '@lingui/core'; import { i18n } from '@lingui/core'; +import type { MacroMessageDescriptor } from '@lingui/core/macro'; import type { I18nLocaleData, SupportedLanguageCodes } from '../constants/i18n'; import { APP_I18N_OPTIONS } from '../constants/i18n'; @@ -84,3 +85,10 @@ export const extractLocaleData = ({ headers }: ExtractLocaleDataOptions): I18nLo export const parseMessageDescriptor = (_: I18n['_'], value: string | MessageDescriptor) => { return typeof value === 'string' ? value : _(value); }; + +export const parseMessageDescriptorMacro = ( + t: (descriptor: MacroMessageDescriptor) => string, + value: string | MessageDescriptor, +) => { + return typeof value === 'string' ? value : t(value); +}; diff --git a/packages/lib/utils/organisations-claims.ts b/packages/lib/utils/organisations-claims.ts new file mode 100644 index 000000000..54b0855ca --- /dev/null +++ b/packages/lib/utils/organisations-claims.ts @@ -0,0 +1,25 @@ +import type { OrganisationClaim, SubscriptionClaim } from '@prisma/client'; + +export const generateDefaultOrganisationClaims = (): Omit< + OrganisationClaim, + 'id' | 'organisation' | 'createdAt' | 'updatedAt' | 'originalSubscriptionClaimId' +> => { + return { + teamCount: 1, + memberCount: 1, + flags: {}, + }; +}; + +export const generateDefaultSubscriptionClaim = (): Omit< + SubscriptionClaim, + 'id' | 'organisation' | 'createdAt' | 'updatedAt' | 'originalSubscriptionClaimId' +> => { + return { + name: '', + teamCount: 1, + memberCount: 1, + locked: false, + flags: {}, + }; +}; diff --git a/packages/prisma/migrations/20250514114516_demoasdf/migration.sql b/packages/prisma/migrations/20250514114516_demoasdf/migration.sql new file mode 100644 index 000000000..07d622323 --- /dev/null +++ b/packages/prisma/migrations/20250514114516_demoasdf/migration.sql @@ -0,0 +1,69 @@ +/* + Warnings: + + - You are about to drop the column `customerId` on the `User` table. All the data in the column will be lost. + - You are about to drop the `TeamPending` table. If the table is not empty, all the data it contains will be lost. + - A unique constraint covering the columns `[organisationClaimId]` on the table `Organisation` will be added. If there are existing duplicate values, this will fail. + - A unique constraint covering the columns `[organisationId]` on the table `Subscription` will be added. If there are existing duplicate values, this will fail. + - Added the required column `organisationClaimId` to the `Organisation` table without a default value. This is not possible if the table is not empty. + +*/ +-- DropForeignKey +ALTER TABLE "Subscription" DROP CONSTRAINT "Subscription_organisationId_fkey"; + +-- DropForeignKey +ALTER TABLE "TeamPending" DROP CONSTRAINT "TeamPending_ownerUserId_fkey"; + +-- DropIndex +DROP INDEX "User_customerId_key"; + +-- AlterTable +ALTER TABLE "Organisation" ADD COLUMN "organisationClaimId" TEXT NOT NULL; + +-- AlterTable +ALTER TABLE "Subscription" ADD COLUMN "customerId" TEXT; + +-- AlterTable +ALTER TABLE "User" DROP COLUMN "customerId"; + +-- DropTable +DROP TABLE "TeamPending"; + +-- CreateTable +CREATE TABLE "SubscriptionClaim" ( + "id" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "name" TEXT NOT NULL, + "locked" BOOLEAN NOT NULL DEFAULT false, + "teamCount" INTEGER NOT NULL, + "memberCount" INTEGER NOT NULL, + "flags" JSONB NOT NULL, + + CONSTRAINT "SubscriptionClaim_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "OrganisationClaim" ( + "id" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "originalSubscriptionClaimId" TEXT, + "teamCount" INTEGER NOT NULL, + "memberCount" INTEGER NOT NULL, + "flags" JSONB NOT NULL, + + CONSTRAINT "OrganisationClaim_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Organisation_organisationClaimId_key" ON "Organisation"("organisationClaimId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Subscription_organisationId_key" ON "Subscription"("organisationId"); + +-- AddForeignKey +ALTER TABLE "Subscription" ADD CONSTRAINT "Subscription_organisationId_fkey" FOREIGN KEY ("organisationId") REFERENCES "Organisation"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Organisation" ADD CONSTRAINT "Organisation_organisationClaimId_fkey" FOREIGN KEY ("organisationClaimId") REFERENCES "OrganisationClaim"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/packages/prisma/migrations/20250514114520_asdf/migration.sql b/packages/prisma/migrations/20250514114520_asdf/migration.sql new file mode 100644 index 000000000..284984520 --- /dev/null +++ b/packages/prisma/migrations/20250514114520_asdf/migration.sql @@ -0,0 +1,9 @@ +-- Insert default claims +INSERT INTO "SubscriptionClaim" (id, name, "teamCount", "memberCount", locked, flags, "createdAt", "updatedAt") +VALUES + ('free', 'Free', 1, 1, true, '{}', NOW(), NOW()), + ('individual', 'Individual', 1, 1, true, '{"unlimitedDocuments": true}', NOW(), NOW()), + ('pro', 'Teams', 1, 5, true, '{"memberStripeSync": true, "unlimitedDocuments": true, "embedSigning": true}', NOW(), NOW()), + ('platform', 'Platform', 1, 0, true, '{"unlimitedDocuments": true, "embedAuthoring": false, "embedAuthoringWhiteLabel": true, "embedSigning": false, "embedSigningWhiteLabel": true}', NOW(), NOW()), + ('enterprise', 'Enterprise', 0, 0, true, '{"unlimitedDocuments": true, "embedAuthoring": true, "embedAuthoringWhiteLabel": true, "embedSigning": true, "embedSigningWhiteLabel": true, "cfr21": true}', NOW(), NOW()), + ('early-adopter', 'Early Adopter', 0, 0, true, '{"unlimitedDocuments": true, "embedAuthoring": false, "embedAuthoringWhiteLabel": true, "embedSigning": false, "embedSigningWhiteLabel": true}', NOW(), NOW()); diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index ff96b9192..a99bfa1a4 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -1,3 +1,4 @@ +// Todo: orgs remember to add custom migration to apply default claims generator kysely { provider = "prisma-kysely" } @@ -39,7 +40,6 @@ enum Role { model User { id Int @id @default(autoincrement()) name String? - customerId String? @unique email String @unique emailVerified DateTime? password String? // Todo: (RR7) Remove after RR7 migration. @@ -60,8 +60,6 @@ model User { ownedOrganisations Organisation[] organisationMember OrganisationMember[] - ownedPendingTeams TeamPending[] - twoFactorSecret String? twoFactorEnabled Boolean @default(false) twoFactorBackupCodes String? @@ -255,12 +253,44 @@ model Subscription { updatedAt DateTime @updatedAt cancelAtPeriodEnd Boolean @default(false) - organisationId String - organisation Organisation @relation(fields: [organisationId], references: [id]) + customerId String? // Todo: orgs + + organisationId String @unique + organisation Organisation @relation(fields: [organisationId], references: [id], onDelete: Cascade) @@index([organisationId]) } +/// @zod.import(["import { ZClaimFlagsSchema } from '@documenso/lib/types/subscription';"]) +model SubscriptionClaim { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + name String + locked Boolean @default(false) + + teamCount Int + memberCount Int + + flags Json /// [ClaimFlags] @zod.custom.use(ZClaimFlagsSchema) +} + +/// @zod.import(["import { ZClaimFlagsSchema } from '@documenso/lib/types/subscription';"]) +model OrganisationClaim { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + originalSubscriptionClaimId String? + organisation Organisation? + + teamCount Int + memberCount Int + + flags Json /// [ClaimFlags] @zod.custom.use(ZClaimFlagsSchema) +} + model Account { id String @id @default(cuid()) userId Int @@ -550,8 +580,11 @@ model Organisation { url String @unique // todo: constrain avatarImageId String? - customerId String? @unique - subscriptions Subscription[] + customerId String? @unique // Todo: orgs + subscription Subscription? + + organisationClaimId String @unique + organisationClaim OrganisationClaim @relation(fields: [organisationClaimId], references: [id], onDelete: Cascade) members OrganisationMember[] invites OrganisationMemberInvite[] @@ -683,7 +716,7 @@ model OrganisationGlobalSettings { brandingLogo String @default("") brandingUrl String @default("") brandingCompanyDetails String @default("") - brandingHidePoweredBy Boolean @default(false) + brandingHidePoweredBy Boolean @default(false) // Todo: orgs this doesn't seem to be used? } model TeamGlobalSettings { @@ -732,17 +765,6 @@ model Team { teamGlobalSettings TeamGlobalSettings @relation(fields: [teamGlobalSettingsId], references: [id], onDelete: Cascade) } -model TeamPending { - id Int @id @default(autoincrement()) - name String - url String @unique - createdAt DateTime @default(now()) - customerId String @unique - ownerUserId Int - - owner User @relation(fields: [ownerUserId], references: [id], onDelete: Cascade) -} - model TeamEmail { teamId Int @id @unique createdAt DateTime @default(now()) diff --git a/packages/prisma/types/types.d.ts b/packages/prisma/types/types.d.ts index 4cdd6278b..3b12e3efc 100644 --- a/packages/prisma/types/types.d.ts +++ b/packages/prisma/types/types.d.ts @@ -6,12 +6,15 @@ import type { import type { TDocumentEmailSettings } from '@documenso/lib/types/document-email'; import type { TDocumentFormValues } from '@documenso/lib/types/document-form-values'; import type { TFieldMetaNotOptionalSchema } from '@documenso/lib/types/field-meta'; +import type { TClaimFlags } from '@documenso/lib/types/subscription'; /** * Global types for Prisma.Json instances. */ declare global { namespace PrismaJson { + type ClaimFlags = TClaimFlags; + type DocumentFormValues = TDocumentFormValues; type DocumentAuthOptions = TDocumentAuthOptions; type DocumentEmailSettings = TDocumentEmailSettings; diff --git a/packages/trpc/server/admin-router/create-stripe-customer.ts b/packages/trpc/server/admin-router/create-stripe-customer.ts new file mode 100644 index 000000000..7e68e6eaa --- /dev/null +++ b/packages/trpc/server/admin-router/create-stripe-customer.ts @@ -0,0 +1,50 @@ +import { createCustomer } from '@documenso/ee/server-only/stripe/create-customer'; +import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; +import { prisma } from '@documenso/prisma'; + +import { adminProcedure } from '../trpc'; +import { + ZCreateStripeCustomerRequestSchema, + ZCreateStripeCustomerResponseSchema, +} from './create-stripe-customer.types'; + +export const createStripeCustomerRoute = adminProcedure + .input(ZCreateStripeCustomerRequestSchema) + .output(ZCreateStripeCustomerResponseSchema) + .mutation(async ({ input }) => { + const { organisationId } = input; + + const organisation = await prisma.organisation.findUnique({ + where: { + id: organisationId, + }, + include: { + owner: { + select: { + email: true, + name: true, + }, + }, + }, + }); + + if (!organisation) { + throw new AppError(AppErrorCode.NOT_FOUND); + } + + await prisma.$transaction(async (tx) => { + const stripeCustomer = await createCustomer({ + name: organisation.name, + email: organisation.owner.email, + }); + + await tx.organisation.update({ + where: { + id: organisationId, + }, + data: { + customerId: stripeCustomer.id, + }, + }); + }); + }); diff --git a/packages/trpc/server/admin-router/create-stripe-customer.types.ts b/packages/trpc/server/admin-router/create-stripe-customer.types.ts new file mode 100644 index 000000000..1904bca26 --- /dev/null +++ b/packages/trpc/server/admin-router/create-stripe-customer.types.ts @@ -0,0 +1,9 @@ +import { z } from 'zod'; + +export const ZCreateStripeCustomerRequestSchema = z.object({ + organisationId: z.string().describe('The organisation to attach the customer to'), +}); + +export const ZCreateStripeCustomerResponseSchema = z.void(); + +export type TCreateStripeCustomerRequest = z.infer; diff --git a/packages/trpc/server/admin-router/create-subscription-claim.ts b/packages/trpc/server/admin-router/create-subscription-claim.ts new file mode 100644 index 000000000..92fcbe0f5 --- /dev/null +++ b/packages/trpc/server/admin-router/create-subscription-claim.ts @@ -0,0 +1,23 @@ +import { prisma } from '@documenso/prisma'; + +import { adminProcedure } from '../trpc'; +import { + ZCreateSubscriptionClaimRequestSchema, + ZCreateSubscriptionClaimResponseSchema, +} from './create-subscription-claim.types'; + +export const createSubscriptionClaimRoute = adminProcedure + .input(ZCreateSubscriptionClaimRequestSchema) + .output(ZCreateSubscriptionClaimResponseSchema) + .mutation(async ({ input }) => { + const { name, teamCount, memberCount, flags } = input; + + await prisma.subscriptionClaim.create({ + data: { + name, + teamCount, + memberCount, + flags, + }, + }); + }); diff --git a/packages/trpc/server/admin-router/create-subscription-claim.types.ts b/packages/trpc/server/admin-router/create-subscription-claim.types.ts new file mode 100644 index 000000000..779e7e58a --- /dev/null +++ b/packages/trpc/server/admin-router/create-subscription-claim.types.ts @@ -0,0 +1,14 @@ +import { z } from 'zod'; + +import { ZClaimFlagsSchema } from '@documenso/lib/types/subscription'; + +export const ZCreateSubscriptionClaimRequestSchema = z.object({ + name: z.string().min(1), + teamCount: z.number().int().min(0), + memberCount: z.number().int().min(0), + flags: ZClaimFlagsSchema, +}); + +export const ZCreateSubscriptionClaimResponseSchema = z.void(); + +export type TCreateSubscriptionClaimRequest = z.infer; diff --git a/packages/trpc/server/admin-router/delete-subscription-claim.ts b/packages/trpc/server/admin-router/delete-subscription-claim.ts new file mode 100644 index 000000000..41a3b93a8 --- /dev/null +++ b/packages/trpc/server/admin-router/delete-subscription-claim.ts @@ -0,0 +1,37 @@ +import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; +import { prisma } from '@documenso/prisma'; + +import { adminProcedure } from '../trpc'; +import { + ZDeleteSubscriptionClaimRequestSchema, + ZDeleteSubscriptionClaimResponseSchema, +} from './delete-subscription-claim.types'; + +export const deleteSubscriptionClaimRoute = adminProcedure + .input(ZDeleteSubscriptionClaimRequestSchema) + .output(ZDeleteSubscriptionClaimResponseSchema) + .mutation(async ({ input }) => { + const { id } = input; + + const existingClaim = await prisma.subscriptionClaim.findFirst({ + where: { + id, + }, + }); + + if (!existingClaim) { + throw new AppError(AppErrorCode.NOT_FOUND, { message: 'Subscription claim not found' }); + } + + if (existingClaim.locked) { + throw new AppError(AppErrorCode.UNAUTHORIZED, { + message: 'Cannot delete locked subscription claim', + }); + } + + await prisma.subscriptionClaim.delete({ + where: { + id, + }, + }); + }); diff --git a/packages/trpc/server/admin-router/delete-subscription-claim.types.ts b/packages/trpc/server/admin-router/delete-subscription-claim.types.ts new file mode 100644 index 000000000..122be091a --- /dev/null +++ b/packages/trpc/server/admin-router/delete-subscription-claim.types.ts @@ -0,0 +1,9 @@ +import { z } from 'zod'; + +export const ZDeleteSubscriptionClaimRequestSchema = z.object({ + id: z.string().cuid(), +}); + +export const ZDeleteSubscriptionClaimResponseSchema = z.void(); + +export type TDeleteSubscriptionClaimRequest = z.infer; diff --git a/packages/trpc/server/admin-router/find-admin-organisations.ts b/packages/trpc/server/admin-router/find-admin-organisations.ts new file mode 100644 index 000000000..9375d809a --- /dev/null +++ b/packages/trpc/server/admin-router/find-admin-organisations.ts @@ -0,0 +1,128 @@ +import { Prisma } from '@prisma/client'; + +import type { FindResultResponse } from '@documenso/lib/types/search-params'; +import { prisma } from '@documenso/prisma'; + +import { adminProcedure } from '../trpc'; +import { + ZFindAdminOrganisationsRequestSchema, + ZFindAdminOrganisationsResponseSchema, +} from './find-admin-organisations.types'; + +export const findAdminOrganisationsRoute = adminProcedure + .input(ZFindAdminOrganisationsRequestSchema) + .output(ZFindAdminOrganisationsResponseSchema) + .query(async ({ input }) => { + const { query, page, perPage } = input; + + return await findAdminOrganisations({ + query, + page, + perPage, + }); + }); + +type FindAdminOrganisationsOptions = { + query?: string; + page?: number; + perPage?: number; +}; + +export const findAdminOrganisations = async ({ + query, + page = 1, + perPage = 10, +}: FindAdminOrganisationsOptions) => { + let whereClause: Prisma.OrganisationWhereInput = {}; + + if (query) { + whereClause = { + OR: [ + { + id: { + contains: query, + mode: Prisma.QueryMode.insensitive, + }, + }, + { + owner: { + email: { + contains: query, + mode: Prisma.QueryMode.insensitive, + }, + }, + }, + { + customerId: { + contains: query, + mode: Prisma.QueryMode.insensitive, + }, + }, + { + name: { + contains: query, + mode: Prisma.QueryMode.insensitive, + }, + }, + ], + }; + } + + if (query && query.startsWith('claim:')) { + whereClause = { + organisationClaim: { + originalSubscriptionClaimId: { + contains: query.slice(6), + mode: Prisma.QueryMode.insensitive, + }, + }, + }; + } + + if (query && query.startsWith('org_')) { + whereClause = { + url: { + equals: query, + mode: Prisma.QueryMode.insensitive, + }, + }; + } + + const [data, count] = await Promise.all([ + prisma.organisation.findMany({ + where: whereClause, + skip: Math.max(page - 1, 0) * perPage, + take: perPage, + orderBy: { + createdAt: 'desc', + }, + select: { + id: true, + createdAt: true, + updatedAt: true, + name: true, + url: true, + customerId: true, + owner: { + select: { + id: true, + email: true, + name: true, + }, + }, + subscription: true, + }, + }), + prisma.organisation.count({ + where: whereClause, + }), + ]); + + return { + data, + count, + currentPage: Math.max(page, 1), + perPage, + totalPages: Math.ceil(count / perPage), + } satisfies FindResultResponse; +}; diff --git a/packages/trpc/server/admin-router/find-admin-organisations.types.ts b/packages/trpc/server/admin-router/find-admin-organisations.types.ts new file mode 100644 index 000000000..de2f0a349 --- /dev/null +++ b/packages/trpc/server/admin-router/find-admin-organisations.types.ts @@ -0,0 +1,39 @@ +import type { z } from 'zod'; + +import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params'; +import OrganisationSchema from '@documenso/prisma/generated/zod/modelSchema/OrganisationSchema'; +import SubscriptionSchema from '@documenso/prisma/generated/zod/modelSchema/SubscriptionSchema'; +import UserSchema from '@documenso/prisma/generated/zod/modelSchema/UserSchema'; + +export const ZFindAdminOrganisationsRequestSchema = ZFindSearchParamsSchema; + +export const ZFindAdminOrganisationsResponseSchema = ZFindResultResponse.extend({ + data: OrganisationSchema.pick({ + id: true, + createdAt: true, + updatedAt: true, + name: true, + url: true, + customerId: true, + }) + .extend({ + owner: UserSchema.pick({ + id: true, + email: true, + name: true, + }), + subscription: SubscriptionSchema.pick({ + status: true, + id: true, + planId: true, + priceId: true, + periodEnd: true, + createdAt: true, + updatedAt: true, + cancelAtPeriodEnd: true, + }).nullable(), + }) + .array(), +}); + +export type TFindAdminOrganisationsResponse = z.infer; diff --git a/packages/trpc/server/admin-router/find-subscription-claims.ts b/packages/trpc/server/admin-router/find-subscription-claims.ts new file mode 100644 index 000000000..1e2f81309 --- /dev/null +++ b/packages/trpc/server/admin-router/find-subscription-claims.ts @@ -0,0 +1,76 @@ +import type { Prisma } from '@prisma/client'; +import type { z } from 'zod'; + +import type { FindResultResponse } from '@documenso/lib/types/search-params'; +import { prisma } from '@documenso/prisma'; +import type SubscriptionClaimSchema from '@documenso/prisma/generated/zod/modelSchema/SubscriptionClaimSchema'; + +import { adminProcedure } from '../trpc'; +import { + ZFindSubscriptionClaimsRequestSchema, + ZFindSubscriptionClaimsResponseSchema, +} from './find-subscription-claims.types'; + +export const findSubscriptionClaimsRoute = adminProcedure + .input(ZFindSubscriptionClaimsRequestSchema) + .output(ZFindSubscriptionClaimsResponseSchema) + .query(async ({ input }) => { + const { query, page, perPage } = input; + + return await findSubscriptionClaims({ query, page, perPage }); + }); + +type FindSubscriptionClaimsOptions = { + query?: string; + page?: number; + perPage?: number; +}; + +export const findSubscriptionClaims = async ({ + query, + page = 1, + perPage = 50, +}: FindSubscriptionClaimsOptions) => { + let whereClause: Prisma.SubscriptionClaimWhereInput = {}; + + if (query) { + whereClause = { + OR: [ + { + id: { + contains: query, + mode: 'insensitive', + }, + }, + { + name: { + contains: query, + mode: 'insensitive', + }, + }, + ], + }; + } + + const [data, count] = await Promise.all([ + prisma.subscriptionClaim.findMany({ + where: whereClause, + skip: Math.max(page - 1, 0) * perPage, + take: perPage, + orderBy: { + createdAt: 'desc', + }, + }), + prisma.subscriptionClaim.count({ + where: whereClause, + }), + ]); + + return { + data, + count, + currentPage: Math.max(page, 1), + perPage, + totalPages: Math.ceil(count / perPage), + } satisfies FindResultResponse[]>; +}; diff --git a/packages/trpc/server/admin-router/find-subscription-claims.types.ts b/packages/trpc/server/admin-router/find-subscription-claims.types.ts new file mode 100644 index 000000000..9c7576dc9 --- /dev/null +++ b/packages/trpc/server/admin-router/find-subscription-claims.types.ts @@ -0,0 +1,22 @@ +import type { z } from 'zod'; + +import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params'; +import SubscriptionClaimSchema from '@documenso/prisma/generated/zod/modelSchema/SubscriptionClaimSchema'; + +export const ZFindSubscriptionClaimsRequestSchema = ZFindSearchParamsSchema.extend({}); + +export const ZFindSubscriptionClaimsResponseSchema = ZFindResultResponse.extend({ + data: SubscriptionClaimSchema.pick({ + id: true, + createdAt: true, + updatedAt: true, + name: true, + teamCount: true, + memberCount: true, + locked: true, + flags: true, + }).array(), +}); + +export type TFindSubscriptionClaimsRequest = z.infer; +export type TFindSubscriptionClaimsResponse = z.infer; diff --git a/packages/trpc/server/admin-router/get-admin-organisation.ts b/packages/trpc/server/admin-router/get-admin-organisation.ts new file mode 100644 index 000000000..d7ef4fd23 --- /dev/null +++ b/packages/trpc/server/admin-router/get-admin-organisation.ts @@ -0,0 +1,56 @@ +import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; +import { prisma } from '@documenso/prisma'; + +import { adminProcedure } from '../trpc'; +import { + ZGetAdminOrganisationRequestSchema, + ZGetAdminOrganisationResponseSchema, +} from './get-admin-organisation.types'; + +export const getAdminOrganisationRoute = adminProcedure + .input(ZGetAdminOrganisationRequestSchema) + .output(ZGetAdminOrganisationResponseSchema) + .query(async ({ input }) => { + const { organisationId } = input; + + return await getAdminOrganisation({ + organisationId, + }); + }); + +type GetOrganisationOptions = { + organisationId: string; +}; + +export const getAdminOrganisation = async ({ organisationId }: GetOrganisationOptions) => { + const organisation = await prisma.organisation.findFirst({ + where: { + id: organisationId, + }, + include: { + organisationClaim: true, + organisationGlobalSettings: true, + teams: true, + members: { + include: { + user: { + select: { + id: true, + email: true, + name: true, + }, + }, + }, + }, + subscription: true, + }, + }); + + if (!organisation) { + throw new AppError(AppErrorCode.NOT_FOUND, { + message: 'Organisation not found', + }); + } + + return organisation; +}; diff --git a/packages/trpc/server/admin-router/get-admin-organisation.types.ts b/packages/trpc/server/admin-router/get-admin-organisation.types.ts new file mode 100644 index 000000000..8491003f9 --- /dev/null +++ b/packages/trpc/server/admin-router/get-admin-organisation.types.ts @@ -0,0 +1,38 @@ +import { z } from 'zod'; + +import { ZOrganisationSchema } from '@documenso/lib/types/organisation'; +import OrganisationClaimSchema from '@documenso/prisma/generated/zod/modelSchema/OrganisationClaimSchema'; +import OrganisationGlobalSettingsSchema from '@documenso/prisma/generated/zod/modelSchema/OrganisationGlobalSettingsSchema'; +import OrganisationMemberSchema from '@documenso/prisma/generated/zod/modelSchema/OrganisationMemberSchema'; +import SubscriptionSchema from '@documenso/prisma/generated/zod/modelSchema/SubscriptionSchema'; +import TeamSchema from '@documenso/prisma/generated/zod/modelSchema/TeamSchema'; +import UserSchema from '@documenso/prisma/generated/zod/modelSchema/UserSchema'; + +export const ZGetAdminOrganisationRequestSchema = z.object({ + organisationId: z.string(), +}); + +export const ZGetAdminOrganisationResponseSchema = ZOrganisationSchema.extend({ + organisationGlobalSettings: OrganisationGlobalSettingsSchema, + teams: z.array( + TeamSchema.pick({ + id: true, + name: true, + url: true, + createdAt: true, + avatarImageId: true, + organisationId: true, + }), + ), + members: OrganisationMemberSchema.extend({ + user: UserSchema.pick({ + id: true, + email: true, + name: true, + }), + }).array(), + subscription: SubscriptionSchema.nullable(), + organisationClaim: OrganisationClaimSchema, +}); + +export type TGetAdminOrganisationResponse = z.infer; diff --git a/packages/trpc/server/admin-router/router.ts b/packages/trpc/server/admin-router/router.ts index d4a0c712d..80b570181 100644 --- a/packages/trpc/server/admin-router/router.ts +++ b/packages/trpc/server/admin-router/router.ts @@ -14,6 +14,12 @@ import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id'; import { isDocumentCompleted } from '@documenso/lib/utils/document'; import { adminProcedure, router } from '../trpc'; +import { createStripeCustomerRoute } from './create-stripe-customer'; +import { createSubscriptionClaimRoute } from './create-subscription-claim'; +import { deleteSubscriptionClaimRoute } from './delete-subscription-claim'; +import { findAdminOrganisationsRoute } from './find-admin-organisations'; +import { findSubscriptionClaimsRoute } from './find-subscription-claims'; +import { getAdminOrganisationRoute } from './get-admin-organisation'; import { ZAdminDeleteDocumentMutationSchema, ZAdminDeleteUserMutationSchema, @@ -25,8 +31,30 @@ import { ZAdminUpdateRecipientMutationSchema, ZAdminUpdateSiteSettingMutationSchema, } from './schema'; +import { updateAdminOrganisationRoute } from './update-admin-organisation'; +import { updateSubscriptionClaimRoute } from './update-subscription-claim'; export const adminRouter = router({ + // Todo: orgs Ensure all procedures are admin within this route. + // Todo: orgs Ensure all procedures are admin within this route. + // Todo: orgs Ensure all procedures are admin within this route. + // Todo: orgs Ensure all procedures are admin within this route. + organisation: { + find: findAdminOrganisationsRoute, + get: getAdminOrganisationRoute, + update: updateAdminOrganisationRoute, + }, + claims: { + find: findSubscriptionClaimsRoute, + create: createSubscriptionClaimRoute, + update: updateSubscriptionClaimRoute, + delete: deleteSubscriptionClaimRoute, + }, + stripe: { + createCustomer: createStripeCustomerRoute, + }, + + // Todo: migrate old routes findDocuments: adminProcedure.input(ZAdminFindDocumentsQuerySchema).query(async ({ input }) => { const { query, page, perPage } = input; diff --git a/packages/trpc/server/admin-router/update-admin-organisation.ts b/packages/trpc/server/admin-router/update-admin-organisation.ts new file mode 100644 index 000000000..9c4a85f76 --- /dev/null +++ b/packages/trpc/server/admin-router/update-admin-organisation.ts @@ -0,0 +1,51 @@ +import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; +import { prisma } from '@documenso/prisma'; + +import { adminProcedure } from '../trpc'; +import { + ZUpdateAdminOrganisationRequestSchema, + ZUpdateAdminOrganisationResponseSchema, +} from './update-admin-organisation.types'; + +export const updateAdminOrganisationRoute = adminProcedure + .input(ZUpdateAdminOrganisationRequestSchema) + .output(ZUpdateAdminOrganisationResponseSchema) + .mutation(async ({ input }) => { + const { organisationId, data } = input; + + const organisation = await prisma.organisation.findUnique({ + where: { + id: organisationId, + }, + include: { + organisationClaim: true, + }, + }); + + if (!organisation) { + throw new AppError(AppErrorCode.NOT_FOUND); + } + + const { name, url, customerId, claims, originalSubscriptionClaimId } = data; + + await prisma.organisation.update({ + where: { + id: organisationId, + }, + data: { + name, + url, + customerId: customerId === undefined ? null : customerId, + }, + }); + + await prisma.organisationClaim.update({ + where: { + id: organisation.organisationClaimId, + }, + data: { + ...claims, + originalSubscriptionClaimId, + }, + }); + }); diff --git a/packages/trpc/server/admin-router/update-admin-organisation.types.ts b/packages/trpc/server/admin-router/update-admin-organisation.types.ts new file mode 100644 index 000000000..cb89bf609 --- /dev/null +++ b/packages/trpc/server/admin-router/update-admin-organisation.types.ts @@ -0,0 +1,24 @@ +import { z } from 'zod'; + +import { ZOrganisationNameSchema } from '../organisation-router/create-organisation.types'; +import { ZTeamUrlSchema } from '../team-router/schema'; +import { ZCreateSubscriptionClaimRequestSchema } from './create-subscription-claim.types'; + +export const ZUpdateAdminOrganisationRequestSchema = z.object({ + organisationId: z.string(), + data: z.object({ + name: ZOrganisationNameSchema.optional(), + url: ZTeamUrlSchema.optional(), + claims: ZCreateSubscriptionClaimRequestSchema.pick({ + teamCount: true, + memberCount: true, + flags: true, + }).optional(), + customerId: z.string().optional(), + originalSubscriptionClaimId: z.string().optional(), + }), +}); + +export const ZUpdateAdminOrganisationResponseSchema = z.void(); + +export type TUpdateAdminOrganisationRequest = z.infer; diff --git a/packages/trpc/server/admin-router/update-subscription-claim.ts b/packages/trpc/server/admin-router/update-subscription-claim.ts new file mode 100644 index 000000000..7bf82697e --- /dev/null +++ b/packages/trpc/server/admin-router/update-subscription-claim.ts @@ -0,0 +1,56 @@ +import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; +import type { TClaimFlags } from '@documenso/lib/types/subscription'; +import { prisma } from '@documenso/prisma'; + +import { adminProcedure } from '../trpc'; +import { + ZUpdateSubscriptionClaimRequestSchema, + ZUpdateSubscriptionClaimResponseSchema, +} from './update-subscription-claim.types'; + +export const updateSubscriptionClaimRoute = adminProcedure + .input(ZUpdateSubscriptionClaimRequestSchema) + .output(ZUpdateSubscriptionClaimResponseSchema) + .mutation(async ({ input }) => { + const { id, data } = input; + + const existingClaim = await prisma.subscriptionClaim.findUnique({ + where: { id }, + }); + + if (!existingClaim) { + throw new AppError(AppErrorCode.NOT_FOUND, { message: 'Subscription claim not found' }); + } + + const newlyEnabledFlags = getNewTruthyKeys(existingClaim.flags, data.flags); + + console.log({ + newlyEnabledFlags, + }); + + if (newlyEnabledFlags.length > 0) { + // Todo: orgs backport claims + } + + await prisma.subscriptionClaim.update({ + where: { + id, + }, + data, + }); + }); + +type BoolMap = Record; + +/** + * Get the new truthy keys from the existing flags and the new flags. + * + * @param a - The existing flags. + * @param b - The new flags. + * @returns The new truthy keys. + */ +function getNewTruthyKeys(a: BoolMap, b: BoolMap): (keyof TClaimFlags)[] { + return Object.entries(b) + .filter(([key, value]) => value && !a[key]) + .map(([key]) => key as keyof TClaimFlags); +} diff --git a/packages/trpc/server/admin-router/update-subscription-claim.types.ts b/packages/trpc/server/admin-router/update-subscription-claim.types.ts new file mode 100644 index 000000000..b9ea7b504 --- /dev/null +++ b/packages/trpc/server/admin-router/update-subscription-claim.types.ts @@ -0,0 +1,12 @@ +import { z } from 'zod'; + +import { ZCreateSubscriptionClaimRequestSchema } from './create-subscription-claim.types'; + +export const ZUpdateSubscriptionClaimRequestSchema = z.object({ + id: z.string(), + data: ZCreateSubscriptionClaimRequestSchema, +}); + +export const ZUpdateSubscriptionClaimResponseSchema = z.void(); + +export type TUpdateSubscriptionClaimRequest = z.infer; diff --git a/packages/trpc/server/billing/create-subscription.ts b/packages/trpc/server/billing/create-subscription.ts new file mode 100644 index 000000000..c156c4cb8 --- /dev/null +++ b/packages/trpc/server/billing/create-subscription.ts @@ -0,0 +1,81 @@ +import { createCheckoutSession } from '@documenso/ee/server-only/stripe/create-checkout-session'; +import { createCustomer } from '@documenso/ee/server-only/stripe/create-customer'; +import { IS_BILLING_ENABLED, NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; +import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations'; +import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; +import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations'; +import { prisma } from '@documenso/prisma'; + +import { authenticatedProcedure } from '../trpc'; +import { ZCreateSubscriptionRequestSchema } from './create-subscription.types'; + +export const createSubscriptionRoute = authenticatedProcedure + .input(ZCreateSubscriptionRequestSchema) + .mutation(async ({ ctx, input }) => { + const { organisationId, priceId } = input; + + const userId = ctx.user.id; + + if (!IS_BILLING_ENABLED()) { + throw new AppError(AppErrorCode.INVALID_REQUEST, { + message: 'Billing is not enabled', + }); + } + + const organisation = await prisma.organisation.findFirst({ + where: buildOrganisationWhereQuery( + organisationId, + userId, + ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_BILLING'], + ), + include: { + subscription: true, + owner: { + select: { + email: true, + name: true, + }, + }, + }, + }); + + if (!organisation) { + throw new AppError(AppErrorCode.UNAUTHORIZED); + } + + let customerId = organisation.customerId; + + if (!customerId) { + const customer = await createCustomer({ + name: organisation.name, + email: organisation.owner.email, + }); + + customerId = customer.id; + + await prisma.organisation.update({ + where: { + id: organisationId, + }, + data: { + customerId: customer.id, + }, + }); + } + + const redirectUrl = await createCheckoutSession({ + customerId, + priceId, + returnUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/org/${organisation.url}/settings/billing`, + }); + + if (!redirectUrl) { + throw new AppError(AppErrorCode.UNKNOWN_ERROR, { + message: 'Failed to create checkout session', + }); + } + + return { + redirectUrl, + }; + }); diff --git a/packages/trpc/server/billing/create-subscription.types.ts b/packages/trpc/server/billing/create-subscription.types.ts new file mode 100644 index 000000000..3c0fef0fb --- /dev/null +++ b/packages/trpc/server/billing/create-subscription.types.ts @@ -0,0 +1,6 @@ +import { z } from 'zod'; + +export const ZCreateSubscriptionRequestSchema = z.object({ + organisationId: z.string().describe('The organisation to create the subscription for'), + priceId: z.string().describe('The price to create the subscription for'), +}); diff --git a/packages/trpc/server/billing/get-invoices.ts b/packages/trpc/server/billing/get-invoices.ts new file mode 100644 index 000000000..ac1737c27 --- /dev/null +++ b/packages/trpc/server/billing/get-invoices.ts @@ -0,0 +1,58 @@ +import { getInvoices } from '@documenso/ee/server-only/stripe/get-invoices'; +import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app'; +import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations'; +import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; +import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations'; +import { prisma } from '@documenso/prisma'; + +import { authenticatedProcedure } from '../trpc'; +import { ZGetInvoicesRequestSchema } from './get-invoices.types'; + +export const getInvoicesRoute = authenticatedProcedure + .input(ZGetInvoicesRequestSchema) + .query(async ({ ctx, input }) => { + const { organisationId } = input; + + const userId = ctx.user.id; + + if (!IS_BILLING_ENABLED()) { + throw new AppError(AppErrorCode.INVALID_REQUEST, { + message: 'Billing is not enabled', + }); + } + + const organisation = await prisma.organisation.findFirst({ + where: buildOrganisationWhereQuery( + organisationId, + userId, + ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'], + ), + include: { + subscription: true, + }, + }); + + if (!organisation) { + throw new AppError(AppErrorCode.UNAUTHORIZED, { + message: 'You are not authorized to access this organisation', + }); + } + + if (!organisation.subscription?.customerId) { + return null; + } + + const invoices = await getInvoices({ + customerId: organisation.subscription?.customerId, + }); + + return invoices.data.map((invoice) => ({ + id: invoice.id, + status: invoice.status, + created: invoice.created, + currency: invoice.currency, + total: invoice.total, + hosted_invoice_url: invoice.hosted_invoice_url, + invoice_pdf: invoice.invoice_pdf, + })); + }); diff --git a/packages/trpc/server/billing/get-invoices.types.ts b/packages/trpc/server/billing/get-invoices.types.ts new file mode 100644 index 000000000..77964b968 --- /dev/null +++ b/packages/trpc/server/billing/get-invoices.types.ts @@ -0,0 +1,5 @@ +import { z } from 'zod'; + +export const ZGetInvoicesRequestSchema = z.object({ + organisationId: z.string().describe('The organisation to get the invoices for'), +}); diff --git a/packages/trpc/server/billing/get-plans.ts b/packages/trpc/server/billing/get-plans.ts new file mode 100644 index 000000000..617990eec --- /dev/null +++ b/packages/trpc/server/billing/get-plans.ts @@ -0,0 +1,31 @@ +import { getInternalClaimPlans } from '@documenso/ee/server-only/stripe/get-internal-claim-plans'; +import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app'; +import { prisma } from '@documenso/prisma'; + +import { authenticatedProcedure } from '../trpc'; + +export const getPlansRoute = authenticatedProcedure.query(async ({ ctx }) => { + const userId = ctx.user.id; + + const plans = await getInternalClaimPlans(); + + let canCreateFreeOrganisation = false; + + if (IS_BILLING_ENABLED()) { + const numberOfFreeOrganisations = await prisma.organisation.count({ + where: { + ownerUserId: userId, + subscription: { + is: null, + }, + }, + }); + + canCreateFreeOrganisation = numberOfFreeOrganisations === 0; + } + + return { + plans, + canCreateFreeOrganisation, + }; +}); diff --git a/packages/trpc/server/billing/get-subscription.ts b/packages/trpc/server/billing/get-subscription.ts new file mode 100644 index 000000000..6c24dd455 --- /dev/null +++ b/packages/trpc/server/billing/get-subscription.ts @@ -0,0 +1,34 @@ +import { getInternalClaimPlans } from '@documenso/ee/server-only/stripe/get-internal-claim-plans'; +import { getSubscription } from '@documenso/ee/server-only/stripe/get-subscription'; +import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app'; +import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; + +import { authenticatedProcedure } from '../trpc'; +import { ZGetSubscriptionRequestSchema } from './get-subscription.types'; + +export const getSubscriptionRoute = authenticatedProcedure + .input(ZGetSubscriptionRequestSchema) + .query(async ({ ctx, input }) => { + const { organisationId } = input; + + const userId = ctx.user.id; + + if (!IS_BILLING_ENABLED()) { + throw new AppError(AppErrorCode.INVALID_REQUEST, { + message: 'Billing is not enabled', + }); + } + + const [subscription, plans] = await Promise.all([ + getSubscription({ + organisationId, + userId, + }), + getInternalClaimPlans(), + ]); + + return { + subscription, + plans, + }; + }); diff --git a/packages/trpc/server/billing/get-subscription.types.ts b/packages/trpc/server/billing/get-subscription.types.ts new file mode 100644 index 000000000..c1ef2a2b3 --- /dev/null +++ b/packages/trpc/server/billing/get-subscription.types.ts @@ -0,0 +1,5 @@ +import { z } from 'zod'; + +export const ZGetSubscriptionRequestSchema = z.object({ + organisationId: z.string().describe('The organisation to get the subscription for'), +}); diff --git a/packages/trpc/server/billing/manage-subscription.ts b/packages/trpc/server/billing/manage-subscription.ts new file mode 100644 index 000000000..972f819a2 --- /dev/null +++ b/packages/trpc/server/billing/manage-subscription.ts @@ -0,0 +1,74 @@ +import { createCustomer } from '@documenso/ee/server-only/stripe/create-customer'; +import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-session'; +import { IS_BILLING_ENABLED, NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; +import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations'; +import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; +import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations'; +import { prisma } from '@documenso/prisma'; + +import { authenticatedProcedure } from '../trpc'; +import { ZManageSubscriptionRequestSchema } from './manage-subscription.types'; + +export const manageSubscriptionRoute = authenticatedProcedure + .input(ZManageSubscriptionRequestSchema) + .mutation(async ({ ctx, input }) => { + const { organisationId } = input; + + const userId = ctx.user.id; + + if (!IS_BILLING_ENABLED()) { + throw new AppError(AppErrorCode.INVALID_REQUEST, { + message: 'Billing is not enabled', + }); + } + + const organisation = await prisma.organisation.findFirst({ + where: buildOrganisationWhereQuery( + organisationId, + userId, + ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_BILLING'], + ), + include: { + subscription: true, + owner: { + select: { + email: true, + name: true, + }, + }, + }, + }); + + if (!organisation) { + throw new AppError(AppErrorCode.UNAUTHORIZED); + } + + let customerId = organisation.customerId; + + if (!customerId) { + const customer = await createCustomer({ + name: organisation.name, + email: organisation.owner.email, + }); + + customerId = customer.id; + + await prisma.organisation.update({ + where: { + id: organisationId, + }, + data: { + customerId: customer.id, + }, + }); + } + + const redirectUrl = await getPortalSession({ + customerId, + returnUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/org/${organisation.url}/settings/billing`, + }); + + return { + redirectUrl, + }; + }); diff --git a/packages/trpc/server/billing/manage-subscription.types.ts b/packages/trpc/server/billing/manage-subscription.types.ts new file mode 100644 index 000000000..0d77166ef --- /dev/null +++ b/packages/trpc/server/billing/manage-subscription.types.ts @@ -0,0 +1,5 @@ +import { z } from 'zod'; + +export const ZManageSubscriptionRequestSchema = z.object({ + organisationId: z.string().describe('The organisation to manage the subscription for'), +}); diff --git a/packages/trpc/server/billing/router.ts b/packages/trpc/server/billing/router.ts new file mode 100644 index 000000000..e11a759de --- /dev/null +++ b/packages/trpc/server/billing/router.ts @@ -0,0 +1,20 @@ +import { router } from '../trpc'; +import { createSubscriptionRoute } from './create-subscription'; +import { getInvoicesRoute } from './get-invoices'; +import { getPlansRoute } from './get-plans'; +import { getSubscriptionRoute } from './get-subscription'; +import { manageSubscriptionRoute } from './manage-subscription'; + +export const billingRouter = router({ + plans: { + get: getPlansRoute, + }, + subscription: { + get: getSubscriptionRoute, + create: createSubscriptionRoute, + manage: manageSubscriptionRoute, + }, + invoices: { + get: getInvoicesRoute, + }, +}); diff --git a/packages/trpc/server/document-router/router.ts b/packages/trpc/server/document-router/router.ts index 0f86bef87..6f9bed39b 100644 --- a/packages/trpc/server/document-router/router.ts +++ b/packages/trpc/server/document-router/router.ts @@ -238,7 +238,7 @@ export const documentRouter = router({ .input(ZCreateDocumentV2RequestSchema) .output(ZCreateDocumentV2ResponseSchema) .mutation(async ({ input, ctx }) => { - const { teamId } = ctx; + const { teamId, user } = ctx; const { title, @@ -250,7 +250,7 @@ export const documentRouter = router({ meta, } = input; - const { remaining } = await getServerLimits({ email: ctx.user.email, teamId }); + const { remaining } = await getServerLimits({ userId: user.id, teamId }); if (remaining.documents <= 0) { throw new AppError(AppErrorCode.LIMIT_EXCEEDED, { @@ -307,10 +307,10 @@ export const documentRouter = router({ // }) .input(ZCreateDocumentRequestSchema) .mutation(async ({ input, ctx }) => { - const { teamId } = ctx; + const { teamId, user } = ctx; const { title, documentDataId, timezone } = input; - const { remaining } = await getServerLimits({ email: ctx.user.email, teamId }); + const { remaining } = await getServerLimits({ userId: user.id, teamId }); if (remaining.documents <= 0) { throw new AppError(AppErrorCode.LIMIT_EXCEEDED, { @@ -320,7 +320,7 @@ export const documentRouter = router({ } return await createDocument({ - userId: ctx.user.id, + userId: user.id, teamId, title, documentDataId, diff --git a/packages/trpc/server/organisation-router/create-organisation.ts b/packages/trpc/server/organisation-router/create-organisation.ts index 020f05adf..4e25c13c3 100644 --- a/packages/trpc/server/organisation-router/create-organisation.ts +++ b/packages/trpc/server/organisation-router/create-organisation.ts @@ -1,4 +1,10 @@ +import { createCheckoutSession } from '@documenso/ee/server-only/stripe/create-checkout-session'; +import { createCustomer } from '@documenso/ee/server-only/stripe/create-customer'; +import { IS_BILLING_ENABLED, NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; +import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import { createOrganisation } from '@documenso/lib/server-only/organisation/create-organisation'; +import { generateStripeOrganisationCreateMetadata } from '@documenso/lib/utils/billing'; +import { prisma } from '@documenso/prisma'; import { authenticatedProcedure } from '../trpc'; import { @@ -11,12 +17,60 @@ export const createOrganisationRoute = authenticatedProcedure .input(ZCreateOrganisationRequestSchema) .output(ZCreateOrganisationResponseSchema) .mutation(async ({ input, ctx }) => { - const { name, url } = input; + const { name, priceId } = input; const { user } = ctx; + // Todo: orgs + // Todo: Check if user has reached limit. + if (IS_BILLING_ENABLED() && !priceId) { + const userOrganisations = await prisma.organisation.findMany({ + where: { + ownerUserId: user.id, + subscription: { + is: null, + }, + }, + }); + + if (userOrganisations.length >= 1) { + throw new AppError(AppErrorCode.LIMIT_EXCEEDED, { + message: 'You have reached the maximum number of free organisations.', + }); + } + } + + // Create checkout session for payment. + if (IS_BILLING_ENABLED() && priceId) { + // if (!claimId) { + // throw new AppError(AppErrorCode.INVALID_REQUEST, { + // message: 'Claim ID is required', + // }); + // } + + const customer = await createCustomer({ + email: user.email, + name: user.name || user.email, + }); + + const checkoutUrl = await createCheckoutSession({ + priceId, + customerId: customer.id, + returnUrl: `${NEXT_PUBLIC_WEBAPP_URL()}/settings/organisations`, + subscriptionMetadata: generateStripeOrganisationCreateMetadata(name, user.id), + }); + + return { + paymentRequired: true, + checkoutUrl, + }; + } + await createOrganisation({ userId: user.id, name, - url, }); + + return { + paymentRequired: false, + }; }); diff --git a/packages/trpc/server/organisation-router/create-organisation.types.ts b/packages/trpc/server/organisation-router/create-organisation.types.ts index a9eec5875..e18846120 100644 --- a/packages/trpc/server/organisation-router/create-organisation.types.ts +++ b/packages/trpc/server/organisation-router/create-organisation.types.ts @@ -1,7 +1,5 @@ import { z } from 'zod'; -import { ZTeamUrlSchema } from '../team-router/schema'; - // export const createOrganisationMeta: TrpcOpenApiMeta = { // openapi: { // method: 'POST', @@ -19,7 +17,17 @@ export const ZOrganisationNameSchema = z export const ZCreateOrganisationRequestSchema = z.object({ name: ZOrganisationNameSchema, - url: ZTeamUrlSchema, + priceId: z.string().optional(), }); -export const ZCreateOrganisationResponseSchema = z.void(); +export const ZCreateOrganisationResponseSchema = z.union([ + z.object({ + paymentRequired: z.literal(false), + }), + z.object({ + paymentRequired: z.literal(true), + checkoutUrl: z.string(), + }), +]); + +export type TCreateOrganisationResponse = z.infer; diff --git a/packages/trpc/server/organisation-router/delete-organisation-member-invites.ts b/packages/trpc/server/organisation-router/delete-organisation-member-invites.ts index 41a94dd89..65ef1378f 100644 --- a/packages/trpc/server/organisation-router/delete-organisation-member-invites.ts +++ b/packages/trpc/server/organisation-router/delete-organisation-member-invites.ts @@ -1,5 +1,7 @@ +import { syncMemberCountWithStripeSeatPlan } from '@documenso/ee/server-only/stripe/update-subscription-item-quantity'; import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; +import { validateIfSubscriptionIsRequired } from '@documenso/lib/utils/billing'; import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations'; import { prisma } from '@documenso/prisma'; @@ -23,12 +25,42 @@ export const deleteOrganisationMemberInvitesRoute = authenticatedProcedure userId, ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'], ), + include: { + organisationClaim: true, + subscription: true, + members: { + select: { + id: true, + }, + }, + invites: { + select: { + id: true, + }, + }, + }, }); if (!organisation) { throw new AppError(AppErrorCode.NOT_FOUND); } + const { organisationClaim } = organisation; + + const subscription = validateIfSubscriptionIsRequired(organisation.subscription); + + const numberOfCurrentMembers = organisation.members.length; + const numberOfCurrentInvites = organisation.invites.length; + const totalMemberCountWithInvites = numberOfCurrentMembers + numberOfCurrentInvites - 1; + + if (subscription) { + await syncMemberCountWithStripeSeatPlan( + subscription, + organisationClaim, + totalMemberCountWithInvites, + ); + } + await prisma.organisationMemberInvite.deleteMany({ where: { id: { diff --git a/packages/trpc/server/organisation-router/delete-organisation-members.ts b/packages/trpc/server/organisation-router/delete-organisation-members.ts index f8b42ff5d..11fa74522 100644 --- a/packages/trpc/server/organisation-router/delete-organisation-members.ts +++ b/packages/trpc/server/organisation-router/delete-organisation-members.ts @@ -1,7 +1,10 @@ +import { syncMemberCountWithStripeSeatPlan } from '@documenso/ee/server-only/stripe/update-subscription-item-quantity'; import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; +import { validateIfSubscriptionIsRequired } from '@documenso/lib/utils/billing'; import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations'; import { prisma } from '@documenso/prisma'; +import { OrganisationMemberInviteStatus } from '@documenso/prisma/client'; import { authenticatedProcedure } from '../trpc'; import { @@ -30,76 +33,73 @@ type DeleteOrganisationMembersProps = { organisationMemberIds: string[]; }; -/** - * Deletes multiple organisation members. - * - * This logic is also used to leave a team (hence strange logic). - */ export const deleteOrganisationMembers = async ({ userId, organisationId, organisationMemberIds, }: DeleteOrganisationMembersProps) => { - const membersToDelete = await prisma.organisationMember.findMany({ - where: { - id: { - in: organisationMemberIds, - }, + const organisation = await prisma.organisation.findFirst({ + where: buildOrganisationWhereQuery( organisationId, + userId, + ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'], + ), + include: { + subscription: true, + organisationClaim: true, + members: { + select: { + id: true, + userId: true, + }, + }, + invites: { + where: { + status: OrganisationMemberInviteStatus.PENDING, + }, + select: { + id: true, + }, + }, }, }); - // Prevent the user from deleting other users if they do not have permission. - if (membersToDelete.some((member) => member.userId !== userId)) { - const organisation = await prisma.organisation.findFirst({ - where: buildOrganisationWhereQuery( - organisationId, - userId, - ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP['MANAGE_ORGANISATION'], - ), - }); - - if (!organisation) { - throw new AppError(AppErrorCode.UNAUTHORIZED); - } + if (!organisation) { + throw new AppError(AppErrorCode.UNAUTHORIZED); } - // Todo: Orgs - Handle seats. - await prisma.$transaction( - async (tx) => { - await tx.organisationMember.deleteMany({ - where: { - id: { - in: organisationMemberIds, - }, - organisationId, - }, - }); + const { organisationClaim } = organisation; - // Todo: orgs handle removing groups - - // if (IS_BILLING_ENABLED() && team.subscription) { - // const numberOfSeats = await tx.teamMember.count({ - // where: { - // teamId, - // }, - // }); - - // await updateSubscriptionItemQuantity({ - // priceId: team.subscription.priceId, - // subscriptionId: team.subscription.planId, - // quantity: numberOfSeats, - // }); - // } - - // await jobs.triggerJob({ - // name: 'send.team-member-left.email', - // payload: { - // teamId, - // memberUserId: leavingUser.id, - // }, - // }); - }, - { timeout: 30_000 }, + const membersToDelete = organisation.members.filter((member) => + organisationMemberIds.includes(member.id), ); + + const subscription = validateIfSubscriptionIsRequired(organisation.subscription); + + const inviteCount = organisation.invites.length; + const newMemberCount = organisation.members.length + inviteCount - membersToDelete.length; + + if (subscription) { + await syncMemberCountWithStripeSeatPlan(subscription, organisationClaim, newMemberCount); + } + + await prisma.$transaction(async (tx) => { + await tx.organisationMember.deleteMany({ + where: { + id: { + in: organisationMemberIds, + }, + organisationId, + }, + }); + + // Todo: orgs + // await jobs.triggerJob({ + // name: 'send.team-member-left.email', + // payload: { + // teamId, + // memberUserId: leavingUser.id, + // }, + // }); + }); }; diff --git a/packages/trpc/server/organisation-router/get-organisation-member-invites.ts b/packages/trpc/server/organisation-router/get-organisation-member-invites.ts index 5c105d57e..15f88fdd3 100644 --- a/packages/trpc/server/organisation-router/get-organisation-member-invites.ts +++ b/packages/trpc/server/organisation-router/get-organisation-member-invites.ts @@ -25,6 +25,7 @@ export const getOrganisationMemberInvitesRoute = authenticatedProcedure select: { id: true, name: true, + url: true, avatarImageId: true, }, }, diff --git a/packages/trpc/server/organisation-router/get-organisation-member-invites.types.ts b/packages/trpc/server/organisation-router/get-organisation-member-invites.types.ts index 616d292e5..a1fb29f5f 100644 --- a/packages/trpc/server/organisation-router/get-organisation-member-invites.types.ts +++ b/packages/trpc/server/organisation-router/get-organisation-member-invites.types.ts @@ -19,6 +19,7 @@ export const ZGetOrganisationMemberInvitesResponseSchema = OrganisationMemberInv organisation: OrganisationSchema.pick({ id: true, name: true, + url: true, avatarImageId: true, }), }) diff --git a/packages/trpc/server/organisation-router/get-organisation-session.ts b/packages/trpc/server/organisation-router/get-organisation-session.ts index edddd277c..454bd240f 100644 --- a/packages/trpc/server/organisation-router/get-organisation-session.ts +++ b/packages/trpc/server/organisation-router/get-organisation-session.ts @@ -29,6 +29,7 @@ export const getOrganisationSession = async ({ }, }, include: { + organisationClaim: true, groups: { where: { organisationGroupMembers: { diff --git a/packages/trpc/server/organisation-router/get-organisation.ts b/packages/trpc/server/organisation-router/get-organisation.ts index eb46d9186..51037550e 100644 --- a/packages/trpc/server/organisation-router/get-organisation.ts +++ b/packages/trpc/server/organisation-router/get-organisation.ts @@ -44,6 +44,8 @@ export const getOrganisation = async ({ }, include: { organisationGlobalSettings: true, + subscription: true, + organisationClaim: true, teams: { where: { teamGroups: { diff --git a/packages/trpc/server/organisation-router/get-organisation.types.ts b/packages/trpc/server/organisation-router/get-organisation.types.ts index cba6f13fb..39df51b3d 100644 --- a/packages/trpc/server/organisation-router/get-organisation.types.ts +++ b/packages/trpc/server/organisation-router/get-organisation.types.ts @@ -1,7 +1,9 @@ import { z } from 'zod'; import { ZOrganisationSchema } from '@documenso/lib/types/organisation'; +import OrganisationClaimSchema from '@documenso/prisma/generated/zod/modelSchema/OrganisationClaimSchema'; import OrganisationGlobalSettingsSchema from '@documenso/prisma/generated/zod/modelSchema/OrganisationGlobalSettingsSchema'; +import SubscriptionSchema from '@documenso/prisma/generated/zod/modelSchema/SubscriptionSchema'; import TeamSchema from '@documenso/prisma/generated/zod/modelSchema/TeamSchema'; // export const getOrganisationMeta: TrpcOpenApiMeta = { @@ -20,6 +22,8 @@ export const ZGetOrganisationRequestSchema = z.object({ export const ZGetOrganisationResponseSchema = ZOrganisationSchema.extend({ organisationGlobalSettings: OrganisationGlobalSettingsSchema, + organisationClaim: OrganisationClaimSchema, + subscription: SubscriptionSchema.nullable(), teams: z.array( TeamSchema.pick({ id: true, diff --git a/packages/trpc/server/organisation-router/leave-organisation.ts b/packages/trpc/server/organisation-router/leave-organisation.ts new file mode 100644 index 000000000..035489e2a --- /dev/null +++ b/packages/trpc/server/organisation-router/leave-organisation.ts @@ -0,0 +1,75 @@ +import { syncMemberCountWithStripeSeatPlan } from '@documenso/ee/server-only/stripe/update-subscription-item-quantity'; +import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; +import { jobs } from '@documenso/lib/jobs/client'; +import { validateIfSubscriptionIsRequired } from '@documenso/lib/utils/billing'; +import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations'; +import { prisma } from '@documenso/prisma'; +import { OrganisationMemberInviteStatus } from '@documenso/prisma/client'; + +import { authenticatedProcedure } from '../trpc'; +import { + ZLeaveOrganisationRequestSchema, + ZLeaveOrganisationResponseSchema, +} from './leave-organisation.types'; + +export const leaveOrganisationRoute = authenticatedProcedure + .input(ZLeaveOrganisationRequestSchema) + .output(ZLeaveOrganisationResponseSchema) + .mutation(async ({ ctx, input }) => { + const { organisationId } = input; + + const userId = ctx.user.id; + + const organisation = await prisma.organisation.findFirst({ + where: buildOrganisationWhereQuery(organisationId, userId), + include: { + organisationClaim: true, + subscription: true, + invites: { + where: { + status: OrganisationMemberInviteStatus.PENDING, + }, + select: { + id: true, + }, + }, + members: { + select: { + id: true, + }, + }, + }, + }); + + if (!organisation) { + throw new AppError(AppErrorCode.NOT_FOUND); + } + + const { organisationClaim } = organisation; + + const subscription = validateIfSubscriptionIsRequired(organisation.subscription); + + const inviteCount = organisation.invites.length; + const newMemberCount = organisation.members.length + inviteCount - 1; + + if (subscription) { + await syncMemberCountWithStripeSeatPlan(subscription, organisationClaim, newMemberCount); + } + + await prisma.organisationMember.delete({ + where: { + userId_organisationId: { + userId, + organisationId, + }, + }, + }); + + await jobs.triggerJob({ + name: 'send.organisation-member-left.email', + payload: { + organisationId: organisation.id, + memberUserId: userId, + }, + }); + }); diff --git a/packages/trpc/server/organisation-router/leave-organisation.types.ts b/packages/trpc/server/organisation-router/leave-organisation.types.ts new file mode 100644 index 000000000..0dd275618 --- /dev/null +++ b/packages/trpc/server/organisation-router/leave-organisation.types.ts @@ -0,0 +1,7 @@ +import { z } from 'zod'; + +export const ZLeaveOrganisationRequestSchema = z.object({ + organisationId: z.string(), +}); + +export const ZLeaveOrganisationResponseSchema = z.void(); diff --git a/packages/trpc/server/organisation-router/router.ts b/packages/trpc/server/organisation-router/router.ts index df7250acb..e3abe700a 100644 --- a/packages/trpc/server/organisation-router/router.ts +++ b/packages/trpc/server/organisation-router/router.ts @@ -16,6 +16,7 @@ import { getOrganisationRoute } from './get-organisation'; import { getOrganisationMemberInvitesRoute } from './get-organisation-member-invites'; import { getOrganisationSessionRoute } from './get-organisation-session'; import { getOrganisationsRoute } from './get-organisations'; +import { leaveOrganisationRoute } from './leave-organisation'; import { resendOrganisationMemberInviteRoute } from './resend-organisation-member-invite'; import { updateOrganisationRoute } from './update-organisation'; import { updateOrganisationGroupRoute } from './update-organisation-group'; @@ -28,6 +29,7 @@ export const organisationRouter = router({ create: createOrganisationRoute, update: updateOrganisationRoute, delete: deleteOrganisationRoute, + leave: leaveOrganisationRoute, member: { find: findOrganisationMembersRoute, update: updateOrganisationMemberRoute, diff --git a/packages/trpc/server/organisation-router/update-organisation.ts b/packages/trpc/server/organisation-router/update-organisation.ts index f4fbd1822..7e1319225 100644 --- a/packages/trpc/server/organisation-router/update-organisation.ts +++ b/packages/trpc/server/organisation-router/update-organisation.ts @@ -39,7 +39,7 @@ export const updateOrganisationRoute = authenticatedProcedure }, data: { name: data.name, - url: data.url, // Todo: (orgs) check url unique + url: data.url, // Todo: orgs check url unique }, }); }); diff --git a/packages/trpc/server/organisation-router/update-organisation.types.ts b/packages/trpc/server/organisation-router/update-organisation.types.ts index 568e4e6dd..8f1a912ef 100644 --- a/packages/trpc/server/organisation-router/update-organisation.types.ts +++ b/packages/trpc/server/organisation-router/update-organisation.types.ts @@ -1,5 +1,6 @@ import { z } from 'zod'; +import { ZTeamUrlSchema } from '../team-router/schema'; import { ZCreateOrganisationRequestSchema } from './create-organisation.types'; // export const updateOrganisationMeta: TrpcOpenApiMeta = { @@ -15,7 +16,8 @@ import { ZCreateOrganisationRequestSchema } from './create-organisation.types'; export const ZUpdateOrganisationRequestSchema = z.object({ data: ZCreateOrganisationRequestSchema.pick({ name: true, - url: true, + }).extend({ + url: ZTeamUrlSchema, }), organisationId: z.string(), }); diff --git a/packages/trpc/server/profile-router/router.ts b/packages/trpc/server/profile-router/router.ts index 718f6b51d..55174e460 100644 --- a/packages/trpc/server/profile-router/router.ts +++ b/packages/trpc/server/profile-router/router.ts @@ -1,6 +1,4 @@ import { setAvatarImage } from '@documenso/lib/server-only/profile/set-avatar-image'; -import { createBillingPortal } from '@documenso/lib/server-only/user/create-billing-portal'; -import { createCheckoutSession } from '@documenso/lib/server-only/user/create-checkout-session'; import { deleteUser } from '@documenso/lib/server-only/user/delete-user'; import { findUserSecurityAuditLogs } from '@documenso/lib/server-only/user/find-user-security-audit-logs'; import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id'; @@ -8,7 +6,6 @@ import { updateProfile } from '@documenso/lib/server-only/user/update-profile'; import { adminProcedure, authenticatedProcedure, router } from '../trpc'; import { - ZCreateCheckoutSessionRequestSchema, ZFindUserSecurityAuditLogsSchema, ZRetrieveUserByIdQuerySchema, ZSetProfileImageMutationSchema, @@ -31,31 +28,6 @@ export const profileRouter = router({ return await getUserById({ id }); }), - createBillingPortal: authenticatedProcedure.mutation(async ({ ctx }) => { - return await createBillingPortal({ - user: { - id: ctx.user.id, - customerId: ctx.user.customerId, - email: ctx.user.email, - name: ctx.user.name, - }, - }); - }), - - createCheckoutSession: authenticatedProcedure - .input(ZCreateCheckoutSessionRequestSchema) - .mutation(async ({ ctx, input }) => { - return await createCheckoutSession({ - user: { - id: ctx.user.id, - customerId: ctx.user.customerId, - email: ctx.user.email, - name: ctx.user.name, - }, - priceId: input.priceId, - }); - }), - updateProfile: authenticatedProcedure .input(ZUpdateProfileMutationSchema) .mutation(async ({ input, ctx }) => { diff --git a/packages/trpc/server/profile-router/schema.ts b/packages/trpc/server/profile-router/schema.ts index 395ebcb24..661182dfe 100644 --- a/packages/trpc/server/profile-router/schema.ts +++ b/packages/trpc/server/profile-router/schema.ts @@ -13,10 +13,6 @@ export const ZRetrieveUserByIdQuerySchema = z.object({ export type TRetrieveUserByIdQuerySchema = z.infer; -export const ZCreateCheckoutSessionRequestSchema = z.object({ - priceId: z.string().min(1), -}); - export const ZUpdateProfileMutationSchema = z.object({ name: z.string().min(1), signature: z.string(), diff --git a/packages/trpc/server/router.ts b/packages/trpc/server/router.ts index 0eb58974b..9cf2c8389 100644 --- a/packages/trpc/server/router.ts +++ b/packages/trpc/server/router.ts @@ -1,6 +1,7 @@ import { adminRouter } from './admin-router/router'; import { apiTokenRouter } from './api-token-router/router'; import { authRouter } from './auth-router/router'; +import { billingRouter } from './billing/router'; import { documentRouter } from './document-router/router'; import { fieldRouter } from './field-router/router'; import { organisationRouter } from './organisation-router/router'; @@ -14,6 +15,7 @@ import { webhookRouter } from './webhook-router/router'; export const appRouter = router({ auth: authRouter, + billing: billingRouter, profile: profileRouter, document: documentRouter, field: fieldRouter, diff --git a/packages/trpc/server/team-router/create-team.types.ts b/packages/trpc/server/team-router/create-team.types.ts index 22e854296..0e9ebc26c 100644 --- a/packages/trpc/server/team-router/create-team.types.ts +++ b/packages/trpc/server/team-router/create-team.types.ts @@ -24,15 +24,6 @@ export const ZCreateTeamRequestSchema = z.object({ ), }); -export const ZCreateTeamResponseSchema = z.union([ - z.object({ - paymentRequired: z.literal(false), - }), - z.object({ - paymentRequired: z.literal(true), - pendingTeamId: z.number(), - }), -]); +export const ZCreateTeamResponseSchema = z.void(); export type TCreateTeamRequest = z.infer; -export type TCreateTeamResponse = z.infer; diff --git a/packages/trpc/server/team-router/router.ts b/packages/trpc/server/team-router/router.ts index 917727ded..2ae37b7cc 100644 --- a/packages/trpc/server/team-router/router.ts +++ b/packages/trpc/server/team-router/router.ts @@ -1,13 +1,9 @@ import { TRPCError } from '@trpc/server'; -import { getTeamPrices } from '@documenso/ee/server-only/stripe/get-team-prices'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; -import { createTeamPendingCheckoutSession } from '@documenso/lib/server-only/team/create-team-checkout-session'; import { createTeamEmailVerification } from '@documenso/lib/server-only/team/create-team-email-verification'; import { deleteTeamEmail } from '@documenso/lib/server-only/team/delete-team-email'; import { deleteTeamEmailVerification } from '@documenso/lib/server-only/team/delete-team-email-verification'; -import { deleteTeamPending } from '@documenso/lib/server-only/team/delete-team-pending'; -import { findTeamsPending } from '@documenso/lib/server-only/team/find-teams-pending'; import { getTeamEmailByEmail } from '@documenso/lib/server-only/team/get-team-email-by-email'; import { resendTeamEmailVerification } from '@documenso/lib/server-only/team/resend-team-email-verification'; import { updateTeamEmail } from '@documenso/lib/server-only/team/update-team-email'; @@ -27,11 +23,8 @@ import { getTeamRoute } from './get-team'; import { getTeamMembersRoute } from './get-team-members'; import { ZCreateTeamEmailVerificationMutationSchema, - ZCreateTeamPendingCheckoutMutationSchema, ZDeleteTeamEmailMutationSchema, ZDeleteTeamEmailVerificationMutationSchema, - ZDeleteTeamPendingMutationSchema, - ZFindTeamsPendingQuerySchema, ZResendTeamEmailVerificationMutationSchema, ZUpdateTeamEmailMutationSchema, ZUpdateTeamPublicProfileMutationSchema, @@ -65,41 +58,61 @@ export const teamRouter = router({ }, // Old routes (to be migrated) - - // Internal endpoint for now. - createTeamEmailVerification: authenticatedProcedure - // .meta({ - // openapi: { - // method: 'POST', - // path: '/team/{teamId}/email/create', - // summary: 'Create team email', - // description: 'Add an email to a team and send an email request to verify it', - // tags: ['Teams'], - // }, - // }) - .input(ZCreateTeamEmailVerificationMutationSchema) - .mutation(async ({ input, ctx }) => { - return await createTeamEmailVerification({ - teamId: input.teamId, - userId: ctx.user.id, - data: { - email: input.email, - name: input.name, - }, - }); + // Todo: Refactor into routes. + email: { + get: authenticatedProcedure.query(async ({ ctx }) => { + return await getTeamEmailByEmail({ email: ctx.user.email }); }), + update: authenticatedProcedure + .input(ZUpdateTeamEmailMutationSchema) + .mutation(async ({ input, ctx }) => { + return await updateTeamEmail({ + userId: ctx.user.id, + ...input, + }); + }), + delete: authenticatedProcedure + .input(ZDeleteTeamEmailMutationSchema) + .mutation(async ({ input, ctx }) => { + return await deleteTeamEmail({ + userId: ctx.user.id, + userEmail: ctx.user.email, + ...input, + }); + }), + verification: { + send: authenticatedProcedure + .input(ZCreateTeamEmailVerificationMutationSchema) + .mutation(async ({ input, ctx }) => { + return await createTeamEmailVerification({ + teamId: input.teamId, + userId: ctx.user.id, + data: { + email: input.email, + name: input.name, + }, + }); + }), + resend: authenticatedProcedure + .input(ZResendTeamEmailVerificationMutationSchema) + .mutation(async ({ input, ctx }) => { + await resendTeamEmailVerification({ + userId: ctx.user.id, + ...input, + }); + }), + delete: authenticatedProcedure + .input(ZDeleteTeamEmailVerificationMutationSchema) + .mutation(async ({ input, ctx }) => { + return await deleteTeamEmailVerification({ + userId: ctx.user.id, + ...input, + }); + }), + }, + }, - // Todo: Public endpoint. updateTeamPublicProfile: authenticatedProcedure - // .meta({ - // openapi: { - // method: 'POST', - // path: '/team/{teamId}/profile', - // summary: 'Update a team public profile', - // description: '', - // tags: ['Teams'], - // }, - // }) .input(ZUpdateTeamPublicProfileMutationSchema) .mutation(async ({ input, ctx }) => { try { @@ -129,137 +142,4 @@ export const teamRouter = router({ }); } }), - - // Todo - getTeamEmailByEmail: authenticatedProcedure.query(async ({ ctx }) => { - return await getTeamEmailByEmail({ email: ctx.user.email }); - }), - - // Internal endpoint for now. - updateTeamEmail: authenticatedProcedure - // .meta({ - // openapi: { - // method: 'POST', - // path: '/team/{teamId}/email', - // summary: 'Update a team email', - // description: '', - // tags: ['Teams'], - // }, - // }) - .input(ZUpdateTeamEmailMutationSchema) - .mutation(async ({ input, ctx }) => { - return await updateTeamEmail({ - userId: ctx.user.id, - ...input, - }); - }), - - // Internal endpoint for now. - deleteTeamEmail: authenticatedProcedure - // .meta({ - // openapi: { - // method: 'POST', - // path: '/team/{teamId}/email/delete', - // summary: 'Delete team email', - // description: '', - // tags: ['Teams'], - // }, - // }) - .input(ZDeleteTeamEmailMutationSchema) - .mutation(async ({ input, ctx }) => { - return await deleteTeamEmail({ - userId: ctx.user.id, - userEmail: ctx.user.email, - ...input, - }); - }), - - // Internal endpoint for now. - resendTeamEmailVerification: authenticatedProcedure - // .meta({ - // openapi: { - // method: 'POST', - // path: '/team/{teamId}/email/resend', - // summary: 'Resend team email verification', - // tags: ['Teams'], - // }, - // }) - .input(ZResendTeamEmailVerificationMutationSchema) - .mutation(async ({ input, ctx }) => { - await resendTeamEmailVerification({ - userId: ctx.user.id, - ...input, - }); - }), - - // Internal endpoint for now. - deleteTeamEmailVerification: authenticatedProcedure - // .meta({ - // openapi: { - // method: 'POST', - // path: '/team/{teamId}/email/verify/delete', - // summary: 'Delete team email verification', - // tags: ['Teams'], - // }, - // }) - .input(ZDeleteTeamEmailVerificationMutationSchema) - .mutation(async ({ input, ctx }) => { - return await deleteTeamEmailVerification({ - userId: ctx.user.id, - ...input, - }); - }), - - // Internal endpoint for now. - createTeamPendingCheckout: authenticatedProcedure - .input(ZCreateTeamPendingCheckoutMutationSchema) - .mutation(async ({ input, ctx }) => { - return await createTeamPendingCheckoutSession({ - userId: ctx.user.id, - ...input, - }); - }), - - // Internal endpoint for now. - getTeamPrices: authenticatedProcedure.query(async () => { - return await getTeamPrices(); - }), - - // Internal endpoint for now. - findTeamsPending: authenticatedProcedure - // .meta({ - // openapi: { - // method: 'GET', - // path: '/team/pending', - // summary: 'Find pending teams', - // description: 'Find teams that are pending payment', - // tags: ['Teams'], - // }, - // }) - .input(ZFindTeamsPendingQuerySchema) - .query(async ({ input, ctx }) => { - return await findTeamsPending({ - userId: ctx.user.id, - ...input, - }); - }), - - // Internal endpoint for now. - deleteTeamPending: authenticatedProcedure - // .meta({ - // openapi: { - // method: 'POST', - // path: '/team/pending/{pendingTeamId}/delete', - // summary: 'Delete pending team', - // description: '', - // tags: ['Teams'], - // }, - // }) - .input(ZDeleteTeamPendingMutationSchema) - .mutation(async ({ input, ctx }) => { - return await deleteTeamPending({ - userId: ctx.user.id, - ...input, - }); - }), }); diff --git a/packages/trpc/server/team-router/schema.ts b/packages/trpc/server/team-router/schema.ts index 96064ffb2..2f4ea0a64 100644 --- a/packages/trpc/server/team-router/schema.ts +++ b/packages/trpc/server/team-router/schema.ts @@ -2,7 +2,6 @@ import { TeamMemberRole } from '@prisma/client'; import { z } from 'zod'; import { PROTECTED_TEAM_URLS } from '@documenso/lib/constants/teams'; -import { ZFindSearchParamsSchema } from '@documenso/lib/types/search-params'; export const MAX_PROFILE_BIO_LENGTH = 256; @@ -49,11 +48,6 @@ export const ZCreateTeamEmailVerificationMutationSchema = z.object({ email: z.string().trim().email().toLowerCase().min(1, 'Please enter a valid email.'), }); -export const ZCreateTeamPendingCheckoutMutationSchema = z.object({ - interval: z.union([z.literal('monthly'), z.literal('yearly')]), - pendingTeamId: z.number(), -}); - export const ZDeleteTeamEmailMutationSchema = z.object({ teamId: z.number(), }); @@ -62,16 +56,6 @@ export const ZDeleteTeamEmailVerificationMutationSchema = z.object({ teamId: z.number(), }); -export const ZDeleteTeamMutationSchema = z.object({ - teamId: z.number(), -}); - -export const ZDeleteTeamPendingMutationSchema = z.object({ - pendingTeamId: z.number(), -}); - -export const ZFindTeamsPendingQuerySchema = ZFindSearchParamsSchema; - export const ZGetTeamMembersQuerySchema = z.object({ teamId: z.number(), }); @@ -119,13 +103,7 @@ export type TCreateTeamEmailVerificationMutationSchema = z.infer< typeof ZCreateTeamEmailVerificationMutationSchema >; -export type TCreateTeamPendingCheckoutMutationSchema = z.infer< - typeof ZCreateTeamPendingCheckoutMutationSchema ->; export type TDeleteTeamEmailMutationSchema = z.infer; -export type TDeleteTeamMutationSchema = z.infer; -export type TDeleteTeamPendingMutationSchema = z.infer; -export type TFindTeamsPendingQuerySchema = z.infer; export type TGetTeamMembersQuerySchema = z.infer; export type TUpdateTeamEmailMutationSchema = z.infer; export type TResendTeamEmailVerificationMutationSchema = z.infer< diff --git a/packages/ui/primitives/document-flow/add-settings.tsx b/packages/ui/primitives/document-flow/add-settings.tsx index 2f1534b7e..838fb1d7a 100644 --- a/packages/ui/primitives/document-flow/add-settings.tsx +++ b/packages/ui/primitives/document-flow/add-settings.tsx @@ -9,6 +9,7 @@ import { InfoIcon } from 'lucide-react'; import { useForm } from 'react-hook-form'; import { match } from 'ts-pattern'; +import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats'; import { DOCUMENT_SIGNATURE_TYPES } from '@documenso/lib/constants/document'; import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n'; @@ -66,7 +67,6 @@ export type AddSettingsFormProps = { documentFlow: DocumentFlowStep; recipients: Recipient[]; fields: Field[]; - isDocumentEnterprise: boolean; isDocumentPdfLoaded: boolean; document: TDocument; currentTeamMemberRole?: TeamMemberRole; @@ -77,7 +77,6 @@ export const AddSettingsFormPartial = ({ documentFlow, recipients, fields, - isDocumentEnterprise, isDocumentPdfLoaded, document, currentTeamMemberRole, @@ -85,6 +84,8 @@ export const AddSettingsFormPartial = ({ }: AddSettingsFormProps) => { const { t } = useLingui(); + const organisation = useCurrentOrganisation(); + const { documentAuthOption } = extractDocumentAuthMethods({ documentAuth: document.authOptions, }); @@ -268,7 +269,7 @@ export const AddSettingsFormPartial = ({ /> )} - {isDocumentEnterprise && ( + {organisation.organisationClaim.flags.cfr21 && ( void; isDocumentPdfLoaded: boolean; }; @@ -61,7 +61,6 @@ export const AddSignersFormPartial = ({ fields, signingOrder, allowDictateNextSigner, - isDocumentEnterprise, onSubmit, isDocumentPdfLoaded, }: AddSignersFormProps) => { @@ -75,6 +74,8 @@ export const AddSignersFormPartial = ({ const { currentStep, totalSteps, previousStep } = useStep(); + const organisation = useCurrentOrganisation(); + const defaultRecipients = [ { formId: initialId, @@ -623,36 +624,37 @@ export const AddSignersFormPartial = ({ )} /> - {showAdvancedSettings && isDocumentEnterprise && ( - ( - - - - + {showAdvancedSettings && + organisation.organisationClaim.flags.cfr21 && ( + ( + + + + - - - )} - /> - )} + + + )} + /> + )}
- {!alwaysShowAdvancedSettings && isDocumentEnterprise && ( + {!alwaysShowAdvancedSettings && organisation.organisationClaim.flags.cfr21 && (
, 'size'> { + size?: SpinnerSize; +} + +const Spinner = React.forwardRef( + ({ className, size = 'default', ...props }, ref) => { + return ; + }, +); + +Spinner.displayName = 'Spinner'; + +export interface SpinnerBoxProps extends React.HTMLAttributes { + spinnerProps?: SpinnerProps; +} + +const SpinnerBox = React.forwardRef( + ({ className, spinnerProps, ...props }, ref) => { + return ( +
+ +
+ ); + }, +); + +SpinnerBox.displayName = 'SpinnerBox'; + +export { Spinner, SpinnerBox, spinnerVariants }; diff --git a/packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx b/packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx index 03b3444ce..3acba803b 100644 --- a/packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx +++ b/packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx @@ -12,6 +12,7 @@ import { motion } from 'framer-motion'; import { GripVerticalIcon, HelpCircle, Link2Icon, Plus, Trash } from 'lucide-react'; import { useFieldArray, useForm } from 'react-hook-form'; +import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation'; import { useSession } from '@documenso/lib/client-only/providers/session'; import { ZRecipientAuthOptionsSchema } from '@documenso/lib/types/document-auth'; import { nanoid } from '@documenso/lib/universal/id'; @@ -49,14 +50,12 @@ export type AddTemplatePlaceholderRecipientsFormProps = { signingOrder?: DocumentSigningOrder | null; allowDictateNextSigner?: boolean; templateDirectLink?: TemplateDirectLink | null; - isEnterprise: boolean; onSubmit: (_data: TAddTemplatePlacholderRecipientsFormSchema) => void; isDocumentPdfLoaded: boolean; }; export const AddTemplatePlaceholderRecipientsFormPartial = ({ documentFlow, - isEnterprise, recipients, templateDirectLink, fields, @@ -71,6 +70,8 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({ const { _ } = useLingui(); const { user } = useSession(); + const organisation = useCurrentOrganisation(); + const [placeholderRecipientCount, setPlaceholderRecipientCount] = useState(() => recipients.length > 1 ? recipients.length + 1 : 2, ); @@ -636,29 +637,30 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({ )} /> - {showAdvancedSettings && isEnterprise && ( - ( - - - - + {showAdvancedSettings && + organisation.organisationClaim.flags.cfr21 && ( + ( + + + + - - - )} - /> - )} + + + )} + /> + )}
- {!alwaysShowAdvancedSettings && isEnterprise && ( + {!alwaysShowAdvancedSettings && organisation.organisationClaim.flags.cfr21 && (
{ const { t, i18n } = useLingui(); + const organisation = useCurrentOrganisation(); + const { documentAuthOption } = extractDocumentAuthMethods({ documentAuth: template.authOptions, }); @@ -360,7 +361,7 @@ export const AddTemplateSettingsFormPartial = ({ )} /> - {isEnterprise && ( + {organisation.organisationClaim.flags.cfr21 && (