diff --git a/app/admin/_components/admin-sidebar.tsx b/app/admin/_components/admin-sidebar.tsx
index d7d655c..5b6c230 100644
--- a/app/admin/_components/admin-sidebar.tsx
+++ b/app/admin/_components/admin-sidebar.tsx
@@ -15,6 +15,7 @@ import {
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
+ useSidebar,
} from '@kit/ui/shadcn-sidebar';
import { AppLogo } from '~/components/app-logo';
@@ -22,11 +23,12 @@ import { ProfileAccountDropdownContainer } from '~/components/personal-account-d
export function AdminSidebar() {
const path = usePathname();
+ const { open } = useSidebar();
return (
-
+
@@ -64,4 +66,4 @@ export function AdminSidebar() {
);
-}
+}
\ No newline at end of file
diff --git a/app/admin/accounts/page.tsx b/app/admin/accounts/page.tsx
index d83191b..21c1104 100644
--- a/app/admin/accounts/page.tsx
+++ b/app/admin/accounts/page.tsx
@@ -2,6 +2,7 @@ import { ServerDataLoader } from '@makerkit/data-loader-supabase-nextjs';
import { AdminAccountsTable } from '@kit/admin/components/admin-accounts-table';
import { AdminCreateUserDialog } from '@kit/admin/components/admin-create-user-dialog';
+import { AdminCreateCompanyDialog } from '@kit/admin/components/admin-create-company-dialog';
import { AdminGuard } from '@kit/admin/components/admin-guard';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs';
@@ -30,10 +31,14 @@ async function AccountsPage(props: AdminAccountsPageProps) {
return (
<>
}>
-
+
-
+
+
+
+
+
diff --git a/components/app-logo.tsx b/components/app-logo.tsx
index c2fe283..d11f922 100644
--- a/components/app-logo.tsx
+++ b/components/app-logo.tsx
@@ -4,29 +4,33 @@ import { MedReportLogo } from './med-report-title';
function LogoImage({
className,
+ compact = false,
}: {
className?: string;
width?: number;
+ compact?: boolean;
}) {
- return
;
+ return
;
}
export function AppLogo({
href,
label,
className,
+ compact = false,
}: {
href?: string | null;
className?: string;
label?: string;
+ compact?: boolean;
}) {
if (href === null) {
- return
;
+ return
;
}
return (
-
+
);
}
diff --git a/components/med-report-title.tsx b/components/med-report-title.tsx
index bf809f8..efbea64 100644
--- a/components/med-report-title.tsx
+++ b/components/med-report-title.tsx
@@ -1,11 +1,11 @@
import { cn } from "@/lib/utils";
import { MedReportSmallLogo } from "@/public/assets/med-report-small-logo";
-export const MedReportLogo = ({ className }: { className?: string }) => (
+export const MedReportLogo = ({ className, compact = false }: { className?: string, compact?: boolean }) => (
-
+ {!compact &&
MedReport
-
+ }
);
diff --git a/packages/features/admin/src/components/admin-create-company-dialog.tsx b/packages/features/admin/src/components/admin-create-company-dialog.tsx
new file mode 100644
index 0000000..3dacfed
--- /dev/null
+++ b/packages/features/admin/src/components/admin-create-company-dialog.tsx
@@ -0,0 +1,144 @@
+'use client';
+
+import { useState, useTransition } from 'react';
+
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useForm } from 'react-hook-form';
+
+import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
+import {
+ AlertDialog,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ AlertDialogTrigger,
+} from '@kit/ui/alert-dialog';
+import { Button } from '@kit/ui/button';
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@kit/ui/form';
+import { If } from '@kit/ui/if';
+import { Input } from '@kit/ui/input';
+import { toast } from '@kit/ui/sonner';
+
+import { createTeamAccountAction } from '../lib/server/admin-server-actions';
+import { CreateCompanySchema, CreateCompanySchemaType } from '../lib/server/schema/create-company.schema';
+import { Trans } from '@kit/ui/trans';
+
+export function AdminCreateCompanyDialog(props: React.PropsWithChildren) {
+ const [pending, startTransition] = useTransition();
+ const [error, setError] = useState
(null);
+ const [open, setOpen] = useState(false);
+
+ const form = useForm({
+ resolver: zodResolver(CreateCompanySchema),
+ defaultValues: {
+ name: '',
+ },
+ mode: 'onChange',
+ });
+
+ const onSubmit = (data: CreateCompanySchemaType) => {
+ startTransition(async () => {
+ try {
+ const error = await createTeamAccountAction(data);
+
+ if (!error) {
+ toast.success('Company creates successfully');
+ form.reset();
+
+ setOpen(false);
+ setError(null);
+
+
+ } else {
+ setError('Something went wrong with company creation');
+
+ }
+
+
+ } catch (e) {
+ setError(e instanceof Error ? e.message : 'Error');
+ }
+ });
+ };
+
+ return (
+
+ {props.children}
+
+
+
+ Create New Company Account
+
+
+ Complete the form below to create a new company account.
+
+
+
+
+
+
+
+ );
+}
diff --git a/packages/features/admin/src/lib/server/admin-server-actions.ts b/packages/features/admin/src/lib/server/admin-server-actions.ts
index 260724f..060980d 100644
--- a/packages/features/admin/src/lib/server/admin-server-actions.ts
+++ b/packages/features/admin/src/lib/server/admin-server-actions.ts
@@ -20,6 +20,8 @@ import { ResetPasswordSchema } from './schema/reset-password.schema';
import { createAdminAccountsService } from './services/admin-accounts.service';
import { createAdminAuthUserService } from './services/admin-auth-user.service';
import { adminAction } from './utils/admin-action';
+import { CreateCompanySchema } from './schema/create-company.schema';
+import { createCreateTeamAccountService } from './services/create-team-account.service';
/**
* @name banUserAction
@@ -222,6 +224,42 @@ export const resetPasswordAction = adminAction(
),
);
+export const createTeamAccountAction = enhanceAction(
+ async ({ name }, user) => {
+ const logger = await getLogger();
+ const client = getSupabaseServerClient();
+ const service = createCreateTeamAccountService(client);
+
+ const ctx = {
+ name: 'team-accounts.create',
+ userId: user.id,
+ accountName: name,
+ };
+
+ logger.info(ctx, `Creating company account...`);
+
+ const { data, error } = await service.createNewOrganizationAccount({
+ name,
+ userId: user.id,
+ });
+
+ if (error) {
+ logger.error({ ...ctx, error }, `Failed to create company account`);
+
+ return {
+ error: true,
+ };
+ }
+
+ logger.info(ctx, `Company account created`);
+
+ redirect(`/home/${data.slug}/settings`);
+ },
+ {
+ schema: CreateCompanySchema,
+ },
+);
+
function revalidateAdmin() {
revalidatePath('/admin', 'layout');
}
diff --git a/packages/features/admin/src/lib/server/schema/create-company.schema.ts b/packages/features/admin/src/lib/server/schema/create-company.schema.ts
new file mode 100644
index 0000000..26daa01
--- /dev/null
+++ b/packages/features/admin/src/lib/server/schema/create-company.schema.ts
@@ -0,0 +1,53 @@
+import { z } from 'zod';
+
+/**
+ * @name RESERVED_NAMES_ARRAY
+ * @description Array of reserved names for team accounts
+ * This is a list of names that cannot be used for team accounts as they are reserved for other purposes.
+ * Please include any new reserved names here.
+ */
+const RESERVED_NAMES_ARRAY = [
+ 'settings',
+ 'billing',
+ // please add more reserved names here
+];
+
+const SPECIAL_CHARACTERS_REGEX = /[!@#$%^&*()+=[\]{};':"\\|,.<>/?]/;
+
+/**
+ * @name CompanyNameSchema
+ */
+export const CompanyNameSchema = z
+ .string({
+ description: 'The name of the company account',
+ })
+ .min(2)
+ .max(50)
+ .refine(
+ (name) => {
+ console.log(name);
+ return !SPECIAL_CHARACTERS_REGEX.test(name);
+ },
+ {
+ message: 'teams:specialCharactersError',
+ },
+ )
+ .refine(
+ (name) => {
+ return !RESERVED_NAMES_ARRAY.includes(name.toLowerCase());
+ },
+ {
+ message: 'teams:reservedNameError',
+ },
+ );
+
+/**
+ * @name CreateCompanySchema
+ * @description Schema for creating a team account
+ */
+export const CreateCompanySchema = z.object({
+ name: CompanyNameSchema,
+});
+
+export type CreateCompanySchemaType = z.infer;
+
diff --git a/packages/features/admin/src/lib/server/services/create-team-account.service.ts b/packages/features/admin/src/lib/server/services/create-team-account.service.ts
new file mode 100644
index 0000000..0a0edea
--- /dev/null
+++ b/packages/features/admin/src/lib/server/services/create-team-account.service.ts
@@ -0,0 +1,45 @@
+import 'server-only';
+
+import { SupabaseClient } from '@supabase/supabase-js';
+
+import { getLogger } from '@kit/shared/logger';
+import { Database } from '@kit/supabase/database';
+
+export function createCreateTeamAccountService(
+ client: SupabaseClient,
+) {
+ return new CreateTeamAccountService(client);
+}
+
+class CreateTeamAccountService {
+ private readonly namespace = 'accounts.create-team-account';
+
+ constructor(private readonly client: SupabaseClient) {}
+
+ async createNewOrganizationAccount(params: { name: string; userId: string }) {
+ const logger = await getLogger();
+ const ctx = { ...params, namespace: this.namespace };
+
+ logger.info(ctx, `Creating new company account...`);
+
+ const { error, data } = await this.client.rpc('create_team_account', {
+ account_name: params.name,
+ });
+
+ if (error) {
+ logger.error(
+ {
+ error,
+ ...ctx,
+ },
+ `Error creating company account`,
+ );
+
+ throw new Error('Error creating company account');
+ }
+
+ logger.info(ctx, `Company account created successfully`);
+
+ return { data, error };
+ }
+}