@@ -17,7 +19,7 @@ export function SiteHeader() {
-
+
diff --git a/apps/web/app/(marketing)/components/site-navigation.tsx b/apps/web/app/(marketing)/_components/site-navigation.tsx
similarity index 100%
rename from apps/web/app/(marketing)/components/site-navigation.tsx
rename to apps/web/app/(marketing)/_components/site-navigation.tsx
diff --git a/apps/web/app/(marketing)/components/site-page-header.tsx b/apps/web/app/(marketing)/_components/site-page-header.tsx
similarity index 100%
rename from apps/web/app/(marketing)/components/site-page-header.tsx
rename to apps/web/app/(marketing)/_components/site-page-header.tsx
diff --git a/apps/web/app/(marketing)/blog/[slug]/page.tsx b/apps/web/app/(marketing)/blog/[slug]/page.tsx
index 32f0989c8..581e77720 100644
--- a/apps/web/app/(marketing)/blog/[slug]/page.tsx
+++ b/apps/web/app/(marketing)/blog/[slug]/page.tsx
@@ -5,11 +5,10 @@ import Script from 'next/script';
import { allPosts } from 'contentlayer/generated';
+import Post from '~/(marketing)/blog/_components/post';
import appConfig from '~/config/app.config';
import { withI18n } from '~/lib/i18n/with-i18n';
-import Post from '../components/post';
-
export async function generateMetadata({
params,
}: {
diff --git a/apps/web/app/(marketing)/blog/components/cover-image.tsx b/apps/web/app/(marketing)/blog/_components/cover-image.tsx
similarity index 100%
rename from apps/web/app/(marketing)/blog/components/cover-image.tsx
rename to apps/web/app/(marketing)/blog/_components/cover-image.tsx
diff --git a/apps/web/app/(marketing)/blog/components/date-formatter.tsx b/apps/web/app/(marketing)/blog/_components/date-formatter.tsx
similarity index 100%
rename from apps/web/app/(marketing)/blog/components/date-formatter.tsx
rename to apps/web/app/(marketing)/blog/_components/date-formatter.tsx
diff --git a/apps/web/app/(marketing)/blog/components/draft-post-badge.tsx b/apps/web/app/(marketing)/blog/_components/draft-post-badge.tsx
similarity index 100%
rename from apps/web/app/(marketing)/blog/components/draft-post-badge.tsx
rename to apps/web/app/(marketing)/blog/_components/draft-post-badge.tsx
diff --git a/apps/web/app/(marketing)/blog/components/post-header.tsx b/apps/web/app/(marketing)/blog/_components/post-header.tsx
similarity index 86%
rename from apps/web/app/(marketing)/blog/components/post-header.tsx
rename to apps/web/app/(marketing)/blog/_components/post-header.tsx
index 516ab71bc..45c207520 100644
--- a/apps/web/app/(marketing)/blog/components/post-header.tsx
+++ b/apps/web/app/(marketing)/blog/_components/post-header.tsx
@@ -3,10 +3,10 @@ import type { Post } from 'contentlayer/generated';
import { Heading } from '@kit/ui/heading';
import { If } from '@kit/ui/if';
-import { CoverImage } from '~/(marketing)/blog/components/cover-image';
-import { DateFormatter } from '~/(marketing)/blog/components/date-formatter';
+import { CoverImage } from '~/(marketing)/blog/_components/cover-image';
+import { DateFormatter } from '~/(marketing)/blog/_components/date-formatter';
-const PostHeader: React.FC<{
+export const PostHeader: React.FC<{
post: Post;
}> = ({ post }) => {
const { title, date, readingTime, description, image } = post;
@@ -53,5 +53,3 @@ const PostHeader: React.FC<{
);
};
-
-export default PostHeader;
diff --git a/apps/web/app/(marketing)/blog/components/post-preview.tsx b/apps/web/app/(marketing)/blog/_components/post-preview.tsx
similarity index 89%
rename from apps/web/app/(marketing)/blog/components/post-preview.tsx
rename to apps/web/app/(marketing)/blog/_components/post-preview.tsx
index 3b42e786b..8c113f50c 100644
--- a/apps/web/app/(marketing)/blog/components/post-preview.tsx
+++ b/apps/web/app/(marketing)/blog/_components/post-preview.tsx
@@ -4,8 +4,8 @@ import type { Post } from 'contentlayer/generated';
import { If } from '@kit/ui/if';
-import { CoverImage } from '~/(marketing)/blog/components/cover-image';
-import { DateFormatter } from '~/(marketing)/blog/components/date-formatter';
+import { CoverImage } from '~/(marketing)/blog/_components/cover-image';
+import { DateFormatter } from '~/(marketing)/blog/_components/date-formatter';
type Props = {
post: Post;
@@ -15,7 +15,7 @@ type Props = {
const DEFAULT_IMAGE_HEIGHT = 250;
-function PostPreview({
+export function PostPreview({
post,
preloadImage,
imageHeight,
@@ -67,5 +67,3 @@ function PostPreview({
);
}
-
-export default PostPreview;
diff --git a/apps/web/app/(marketing)/blog/components/post.tsx b/apps/web/app/(marketing)/blog/_components/post.tsx
similarity index 91%
rename from apps/web/app/(marketing)/blog/components/post.tsx
rename to apps/web/app/(marketing)/blog/_components/post.tsx
index 54135fb19..72449b758 100644
--- a/apps/web/app/(marketing)/blog/components/post.tsx
+++ b/apps/web/app/(marketing)/blog/_components/post.tsx
@@ -4,7 +4,7 @@ import type { Post as PostType } from 'contentlayer/generated';
import { Mdx } from '@kit/ui/mdx';
-import PostHeader from './post-header';
+import { PostHeader } from './post-header';
export const Post: React.FC<{
post: PostType;
diff --git a/apps/web/app/(marketing)/blog/page.tsx b/apps/web/app/(marketing)/blog/page.tsx
index ff1f37569..89af584f1 100644
--- a/apps/web/app/(marketing)/blog/page.tsx
+++ b/apps/web/app/(marketing)/blog/page.tsx
@@ -2,19 +2,18 @@ import type { Metadata } from 'next';
import { allPosts } from 'contentlayer/generated';
-import PostPreview from '~/(marketing)/blog/components/post-preview';
-import { SitePageHeader } from '~/(marketing)/components/site-page-header';
+import { GridList } from '~/(marketing)/_components/grid-list';
+import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
+import { PostPreview } from '~/(marketing)/blog/_components/post-preview';
import appConfig from '~/config/app.config';
import { withI18n } from '~/lib/i18n/with-i18n';
-import { GridList } from '../components/grid-list';
-
export const metadata: Metadata = {
title: `Blog - ${appConfig.name}`,
description: `Tutorials, Guides and Updates from our team`,
};
-async function BlogPage() {
+function BlogPage() {
const livePosts = allPosts.filter((post) => {
const isProduction = appConfig.production;
diff --git a/apps/web/app/(marketing)/components/site-header-account-section.tsx b/apps/web/app/(marketing)/components/site-header-account-section.tsx
deleted file mode 100644
index 8870bd103..000000000
--- a/apps/web/app/(marketing)/components/site-header-account-section.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-'use client';
-
-import Link from 'next/link';
-
-import { ChevronRightIcon } from 'lucide-react';
-
-import { PersonalAccountDropdown } from '@kit/accounts/personal-account-dropdown';
-import { useSignOut } from '@kit/supabase/hooks/use-sign-out';
-import { useUserSession } from '@kit/supabase/hooks/use-user-session';
-import { Button } from '@kit/ui/button';
-
-import pathsConfig from '~/config/paths.config';
-
-export function SiteHeaderAccountSection() {
- const signOut = useSignOut();
- const userSession = useUserSession();
-
- if (userSession.data) {
- return (
-
signOut.mutateAsync()}
- />
- );
- }
-
- return ;
-}
-
-function AuthButtons() {
- return (
-
-
-
-
-
-
-
- );
-}
diff --git a/apps/web/app/(marketing)/docs/[...slug]/page.tsx b/apps/web/app/(marketing)/docs/[...slug]/page.tsx
index e7a1b1323..0a4223cae 100644
--- a/apps/web/app/(marketing)/docs/[...slug]/page.tsx
+++ b/apps/web/app/(marketing)/docs/[...slug]/page.tsx
@@ -8,10 +8,10 @@ import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react';
import { If } from '@kit/ui/if';
import { Mdx } from '@kit/ui/mdx';
-import { SitePageHeader } from '~/(marketing)/components/site-page-header';
-import { DocsCards } from '~/(marketing)/docs/components/docs-cards';
-import { DocumentationPageLink } from '~/(marketing)/docs/components/documentation-page-link';
-import { getDocumentationPageTree } from '~/(marketing)/docs/utils/get-documentation-page-tree';
+import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
+import { DocsCards } from '~/(marketing)/docs/_components/docs-cards';
+import { DocumentationPageLink } from '~/(marketing)/docs/_components/documentation-page-link';
+import { getDocumentationPageTree } from '~/(marketing)/docs/_lib/get-documentation-page-tree';
import { withI18n } from '~/lib/i18n/with-i18n';
const getPageBySlug = cache((slug: string) => {
diff --git a/apps/web/app/(marketing)/docs/components/docs-card.tsx b/apps/web/app/(marketing)/docs/_components/docs-card.tsx
similarity index 100%
rename from apps/web/app/(marketing)/docs/components/docs-card.tsx
rename to apps/web/app/(marketing)/docs/_components/docs-card.tsx
diff --git a/apps/web/app/(marketing)/docs/components/docs-cards.tsx b/apps/web/app/(marketing)/docs/_components/docs-cards.tsx
similarity index 100%
rename from apps/web/app/(marketing)/docs/components/docs-cards.tsx
rename to apps/web/app/(marketing)/docs/_components/docs-cards.tsx
diff --git a/apps/web/app/(marketing)/docs/components/docs-navigation.tsx b/apps/web/app/(marketing)/docs/_components/docs-navigation.tsx
similarity index 98%
rename from apps/web/app/(marketing)/docs/components/docs-navigation.tsx
rename to apps/web/app/(marketing)/docs/_components/docs-navigation.tsx
index b76fdefc7..1a9452662 100644
--- a/apps/web/app/(marketing)/docs/components/docs-navigation.tsx
+++ b/apps/web/app/(marketing)/docs/_components/docs-navigation.tsx
@@ -13,7 +13,7 @@ import { Heading } from '@kit/ui/heading';
import { If } from '@kit/ui/if';
import { cn } from '@kit/ui/utils';
-import type { ProcessedDocumentationPage } from '../utils/build-documentation-tree';
+import type { ProcessedDocumentationPage } from '~/(marketing)/docs/_lib/build-documentation-tree';
const DocsNavLink: React.FC<{
label: string;
diff --git a/apps/web/app/(marketing)/docs/components/documentation-page-link.tsx b/apps/web/app/(marketing)/docs/_components/documentation-page-link.tsx
similarity index 100%
rename from apps/web/app/(marketing)/docs/components/documentation-page-link.tsx
rename to apps/web/app/(marketing)/docs/_components/documentation-page-link.tsx
diff --git a/apps/web/app/(marketing)/docs/utils/build-documentation-tree.ts b/apps/web/app/(marketing)/docs/_lib/build-documentation-tree.ts
similarity index 100%
rename from apps/web/app/(marketing)/docs/utils/build-documentation-tree.ts
rename to apps/web/app/(marketing)/docs/_lib/build-documentation-tree.ts
diff --git a/apps/web/app/(marketing)/docs/utils/get-documentation-page-tree.ts b/apps/web/app/(marketing)/docs/_lib/get-documentation-page-tree.ts
similarity index 100%
rename from apps/web/app/(marketing)/docs/utils/get-documentation-page-tree.ts
rename to apps/web/app/(marketing)/docs/_lib/get-documentation-page-tree.ts
diff --git a/apps/web/app/(marketing)/docs/layout.tsx b/apps/web/app/(marketing)/docs/layout.tsx
index 18d0c2727..204352079 100644
--- a/apps/web/app/(marketing)/docs/layout.tsx
+++ b/apps/web/app/(marketing)/docs/layout.tsx
@@ -1,8 +1,8 @@
import type { DocumentationPage } from 'contentlayer/generated';
import { allDocumentationPages } from 'contentlayer/generated';
-import DocsNavigation from './components/docs-navigation';
-import { buildDocumentationTree } from './utils/build-documentation-tree';
+import DocsNavigation from '~/(marketing)/docs/_components/docs-navigation';
+import { buildDocumentationTree } from '~/(marketing)/docs/_lib/build-documentation-tree';
function DocsLayout({ children }: React.PropsWithChildren) {
const tree = buildDocumentationTree(allDocumentationPages);
diff --git a/apps/web/app/(marketing)/docs/page.tsx b/apps/web/app/(marketing)/docs/page.tsx
index 265448225..8455dea81 100644
--- a/apps/web/app/(marketing)/docs/page.tsx
+++ b/apps/web/app/(marketing)/docs/page.tsx
@@ -1,12 +1,11 @@
import { allDocumentationPages } from 'contentlayer/generated';
+import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
+import { DocsCards } from '~/(marketing)/docs/_components/docs-cards';
+import { buildDocumentationTree } from '~/(marketing)/docs/_lib/build-documentation-tree';
import appConfig from '~/config/app.config';
import { withI18n } from '~/lib/i18n/with-i18n';
-import { SitePageHeader } from '../components/site-page-header';
-import { DocsCards } from './components/docs-cards';
-import { buildDocumentationTree } from './utils/build-documentation-tree';
-
export const metadata = {
title: `Documentation - ${appConfig.name}`,
};
diff --git a/apps/web/app/(marketing)/faq/page.tsx b/apps/web/app/(marketing)/faq/page.tsx
index c4c62d7b7..312feeab5 100644
--- a/apps/web/app/(marketing)/faq/page.tsx
+++ b/apps/web/app/(marketing)/faq/page.tsx
@@ -1,9 +1,8 @@
import { ChevronDownIcon } from 'lucide-react';
+import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
import { withI18n } from '~/lib/i18n/with-i18n';
-import { SitePageHeader } from '../components/site-page-header';
-
export const metadata = {
title: 'FAQ',
};
diff --git a/apps/web/app/(marketing)/layout.tsx b/apps/web/app/(marketing)/layout.tsx
index 584451173..b5d4546c6 100644
--- a/apps/web/app/(marketing)/layout.tsx
+++ b/apps/web/app/(marketing)/layout.tsx
@@ -1,12 +1,19 @@
+import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client';
+
+import { SiteFooter } from '~/(marketing)/_components/site-footer';
+import { SiteHeader } from '~/(marketing)/_components/site-header';
import { withI18n } from '~/lib/i18n/with-i18n';
-import { SiteFooter } from './components/site-footer';
-import { SiteHeader } from './components/site-header';
+async function SiteLayout(props: React.PropsWithChildren) {
+ const client = getSupabaseServerComponentClient();
+
+ const {
+ data: { session },
+ } = await client.auth.getSession();
-function SiteLayout(props: React.PropsWithChildren) {
return (
<>
-
+
{props.children}
diff --git a/apps/web/app/(marketing)/pricing/page.tsx b/apps/web/app/(marketing)/pricing/page.tsx
index df15bbd1d..e9474bce3 100644
--- a/apps/web/app/(marketing)/pricing/page.tsx
+++ b/apps/web/app/(marketing)/pricing/page.tsx
@@ -1,11 +1,10 @@
import { PricingTable } from '@kit/billing/components/pricing-table';
+import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
import billingConfig from '~/config/billing.config';
import pathsConfig from '~/config/paths.config';
import { withI18n } from '~/lib/i18n/with-i18n';
-import { SitePageHeader } from '../components/site-page-header';
-
export const metadata = {
title: 'Pricing',
};
diff --git a/apps/web/app/admin/layout.tsx b/apps/web/app/admin/layout.tsx
deleted file mode 100644
index 271a666ca..000000000
--- a/apps/web/app/admin/layout.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { headers } from 'next/headers';
-import { notFound } from 'next/navigation';
-
-import { Page } from '@/components/app/Page';
-
-import AdminSidebar from '../../packages/admin/components/AdminSidebar';
-import isUserSuperAdmin from './utils/is-user-super-admin';
-
-async function AdminLayout({ children }: React.PropsWithChildren) {
- const isAdmin = await isUserSuperAdmin();
-
- if (!isAdmin) {
- notFound();
- }
-
- const csrfToken = headers().get('X-CSRF-Token');
-
- return }>{children};
-}
-
-export default AdminLayout;
diff --git a/apps/web/app/admin/lib/actions-utils.ts b/apps/web/app/admin/lib/actions-utils.ts
deleted file mode 100644
index fd4d68fbe..000000000
--- a/apps/web/app/admin/lib/actions-utils.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { notFound } from 'next/navigation';
-
-import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
-
-import isUserSuperAdmin from '~/admin/utils/is-user-super-admin';
-
-export function withAdminSession(
- fn: (...params: Args) => Response,
-) {
- return async (...params: Args) => {
- const isAdmin = await isUserSuperAdmin({
- client: getSupabaseServerActionClient(),
- });
-
- if (!isAdmin) {
- notFound();
- }
-
- return fn(...params);
- };
-}
diff --git a/apps/web/app/admin/loading.tsx b/apps/web/app/admin/loading.tsx
deleted file mode 100644
index 4ea53181d..000000000
--- a/apps/web/app/admin/loading.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-import { GlobalLoader } from '@kit/ui/global-loader';
-
-export default GlobalLoader;
diff --git a/apps/web/app/admin/organizations/@modal/[uid]/actions.server.ts b/apps/web/app/admin/organizations/@modal/[uid]/actions.server.ts
deleted file mode 100644
index 043de7538..000000000
--- a/apps/web/app/admin/organizations/@modal/[uid]/actions.server.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-'use server';
-
-import { revalidatePath } from 'next/cache';
-import { redirect } from 'next/navigation';
-
-import { Logger } from '@kit/shared/logger';
-import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
-
-import { withAdminSession } from '~/admin/lib/actions-utils';
-
-const getClient = () => getSupabaseServerActionClient({ admin: true });
-
-export const deleteOrganizationAction = withAdminSession(
- async ({ id }: { id: number; csrfToken: string }) => {
- const client = getClient();
-
- Logger.info({ id }, `Admin requested to delete Organization`);
-
- await deleteOrganization(client, {
- organizationId: id,
- });
-
- revalidatePath('/admin/organizations', 'page');
-
- Logger.info({ id }, `Organization account deleted`);
-
- redirect('/admin/organizations');
- },
-);
diff --git a/apps/web/app/admin/organizations/@modal/[uid]/components/DeleteOrganizationModal.tsx b/apps/web/app/admin/organizations/@modal/[uid]/components/DeleteOrganizationModal.tsx
deleted file mode 100644
index ec1d00673..000000000
--- a/apps/web/app/admin/organizations/@modal/[uid]/components/DeleteOrganizationModal.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-'use client';
-
-import { useState, useTransition } from 'react';
-
-import { useRouter } from 'next/navigation';
-
-import type Organization from '@/lib/organizations/types/organization';
-
-import useCsrfToken from '@kit/hooks/use-csrf-token';
-import { Button } from '@kit/ui/button';
-import {
- Dialog,
- DialogContent,
- DialogHeader,
- DialogTitle,
-} from '@kit/ui/dialog';
-import { Input } from '@kit/ui/input';
-import { Label } from '@kit/ui/label';
-
-import { deleteOrganizationAction } from '../actions.server';
-
-function DeleteOrganizationModal({
- organization,
-}: React.PropsWithChildren<{
- organization: Organization;
-}>) {
- const router = useRouter();
- const [isOpen, setIsOpen] = useState(true);
- const [pending, startTransition] = useTransition();
- const csrfToken = useCsrfToken();
-
- const onDismiss = () => {
- router.back();
-
- setIsOpen(false);
- };
-
- const onConfirm = () => {
- startTransition(async () => {
- await deleteOrganizationAction({
- id: organization.id,
- csrfToken,
- });
-
- onDismiss();
- });
- };
-
- return (
-
- );
-}
-
-export default DeleteOrganizationModal;
diff --git a/apps/web/app/admin/organizations/@modal/[uid]/delete/page.tsx b/apps/web/app/admin/organizations/@modal/[uid]/delete/page.tsx
deleted file mode 100644
index c3c63899f..000000000
--- a/apps/web/app/admin/organizations/@modal/[uid]/delete/page.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { getOrganizationByUid } from '@/lib/organizations/database/queries';
-
-import getSupabaseServerComponentClient from '@packages/supabase/server-component-client';
-
-import AdminGuard from '../../../../../../packages/admin/components/AdminGuard';
-import DeleteOrganizationModal from '../components/DeleteOrganizationModal';
-
-interface Params {
- params: {
- uid: string;
- };
-}
-
-async function DeleteOrganizationModalPage({ params }: Params) {
- const client = getSupabaseServerComponentClient({ admin: true });
- const { data, error } = await getOrganizationByUid(client, params.uid);
-
- if (!data || error) {
- throw new Error(`Organization not found`);
- }
-
- return ;
-}
-
-export default AdminGuard(DeleteOrganizationModalPage);
diff --git a/apps/web/app/admin/organizations/@modal/default.tsx b/apps/web/app/admin/organizations/@modal/default.tsx
deleted file mode 100644
index 6ddf1b76f..000000000
--- a/apps/web/app/admin/organizations/@modal/default.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function Default() {
- return null;
-}
diff --git a/apps/web/app/admin/organizations/[uid]/members/components/OrganizationsMembersTable.tsx b/apps/web/app/admin/organizations/[uid]/members/components/OrganizationsMembersTable.tsx
deleted file mode 100644
index 4ac0a5e55..000000000
--- a/apps/web/app/admin/organizations/[uid]/members/components/OrganizationsMembersTable.tsx
+++ /dev/null
@@ -1,137 +0,0 @@
-'use client';
-
-import Link from 'next/link';
-import { usePathname, useRouter } from 'next/navigation';
-
-import { DataTable } from '@/components/app/DataTable';
-import type Membership from '@/lib/organizations/types/membership';
-import type { ColumnDef } from '@tanstack/react-table';
-import { EllipsisVerticalIcon } from 'lucide-react';
-
-import type UserData from '@kit/session/types/user-data';
-import { Button } from '@kit/ui/button';
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from '@kit/ui/dropdown-menu';
-
-import RoleBadge from '../../../../../(app)/[account]/account/organization/components/RoleBadge';
-
-type Data = {
- id: Membership['id'];
- role: Membership['role'];
- user: {
- id: UserData['id'];
- displayName: UserData['displayName'];
- };
-};
-
-const columns: ColumnDef[] = [
- {
- header: 'Membership ID',
- id: 'id',
- accessorKey: 'id',
- },
- {
- header: 'User ID',
- id: 'user-id',
- cell: ({ row }) => {
- const userId = row.original.user.id;
-
- return (
-
- {userId}
-
- );
- },
- },
- {
- header: 'Name',
- id: 'name',
- accessorKey: 'user.displayName',
- },
- {
- header: 'Role',
- cell: ({ row }) => {
- return (
-
-
-
- );
- },
- },
- {
- header: 'Actions',
- cell: ({ row }) => {
- const membership = row.original;
- const userId = membership.user.id;
-
- return (
-
-
-
-
-
-
-
-
- View User
-
-
-
-
- Impersonate User
-
-
-
-
-
- );
- },
- },
-];
-
-function OrganizationsMembersTable({
- memberships,
- page,
- perPage,
- pageCount,
-}: React.PropsWithChildren<{
- memberships: Data[];
- page: number;
- perPage: number;
- pageCount: number;
-}>) {
- const data = memberships.filter((membership) => {
- return membership.user;
- });
-
- const router = useRouter();
- const path = usePathname();
-
- return (
- {
- const { pathname } = new URL(path, window.location.origin);
- const page = pageIndex + 1;
-
- router.push(pathname + '?page=' + page);
- }}
- pageCount={pageCount}
- pageIndex={page - 1}
- pageSize={perPage}
- columns={columns}
- data={data}
- />
- );
-}
-
-export default OrganizationsMembersTable;
diff --git a/apps/web/app/admin/organizations/[uid]/members/page.tsx b/apps/web/app/admin/organizations/[uid]/members/page.tsx
deleted file mode 100644
index 7fdc0b022..000000000
--- a/apps/web/app/admin/organizations/[uid]/members/page.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import { use } from 'react';
-
-import Link from 'next/link';
-
-import { PageBody } from '@/components/app/Page';
-import appConfig from '@/config/app.config';
-import { ChevronRightIcon } from 'lucide-react';
-
-import AdminHeader from '@packages/admin/components/AdminHeader';
-import getSupabaseServerComponentClient from '@packages/supabase/server-component-client';
-
-import getPageFromQueryParams from '../../../utils/get-page-from-query-param';
-import { getMembershipsByOrganizationUid } from '../../queries';
-import OrganizationsMembersTable from './components/OrganizationsMembersTable';
-
-interface AdminMembersPageParams {
- params: {
- uid: string;
- };
-
- searchParams: {
- page?: string;
- };
-}
-
-export const metadata = {
- title: `Members | ${appConfig.name}`,
-};
-
-function AdminMembersPage(params: AdminMembersPageParams) {
- const adminClient = getSupabaseServerComponentClient({ admin: true });
- const uid = params.params.uid;
- const perPage = 20;
- const page = getPageFromQueryParams(params.searchParams.page);
-
- const { data: memberships, count } = use(
- getMembershipsByOrganizationUid(adminClient, { uid, page, perPage }),
- );
-
- const pageCount = count ? Math.ceil(count / perPage) : 0;
-
- return (
-
-
Manage Members
-
-
-
-
-
-
-
-
-
- );
-}
-
-export default AdminMembersPage;
-
-function Breadcrumbs() {
- return (
-
-
- Admin
-
-
-
-
-
Organizations
-
-
-
-
Members
-
- );
-}
diff --git a/apps/web/app/admin/organizations/default.tsx b/apps/web/app/admin/organizations/default.tsx
deleted file mode 100644
index 6ddf1b76f..000000000
--- a/apps/web/app/admin/organizations/default.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function Default() {
- return null;
-}
diff --git a/apps/web/app/admin/organizations/error.tsx b/apps/web/app/admin/organizations/error.tsx
deleted file mode 100644
index 8f64c6fd4..000000000
--- a/apps/web/app/admin/organizations/error.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-'use client';
-
-import { PageBody } from '@/components/app/Page';
-
-import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
-
-function OrganizationsAdminPageError() {
- return (
-
-
- Could not load organizations
-
- There was an error loading the organizations. Please check your
- console errors.
-
-
-
- );
-}
-
-export default OrganizationsAdminPageError;
diff --git a/apps/web/app/admin/organizations/layout.tsx b/apps/web/app/admin/organizations/layout.tsx
deleted file mode 100644
index 36c4ec27e..000000000
--- a/apps/web/app/admin/organizations/layout.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-function OrganizationsLayout(
- props: React.PropsWithChildren<{
- modal: React.ReactNode;
- }>,
-) {
- return (
- <>
- {props.children}
- {props.modal}
- >
- );
-}
-
-export default OrganizationsLayout;
diff --git a/apps/web/app/admin/organizations/page.tsx b/apps/web/app/admin/organizations/page.tsx
deleted file mode 100644
index d0aea73f3..000000000
--- a/apps/web/app/admin/organizations/page.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { PageBody } from '@/components/app/Page';
-import appConfig from '@/config/app.config';
-import AdminGuard from '@/packages/admin/components/AdminGuard';
-import AdminHeader from '@/packages/admin/components/AdminHeader';
-
-import getSupabaseServerComponentClient from '@packages/supabase/server-component-client';
-
-import { Input } from '@kit/ui/input';
-
-import OrganizationsTable from './components/OrganizationsTable';
-import { getOrganizations } from './queries';
-
-interface OrganizationsAdminPageProps {
- searchParams: {
- page?: string;
- search?: string;
- };
-}
-
-export const metadata = {
- title: `Organizations | ${appConfig.name}`,
-};
-
-async function OrganizationsAdminPage({
- searchParams,
-}: OrganizationsAdminPageProps) {
- const page = searchParams.page ? parseInt(searchParams.page, 10) : 1;
- const client = getSupabaseServerComponentClient({ admin: true });
- const perPage = 10;
- const search = searchParams.search || '';
-
- const { organizations, count } = await getOrganizations(
- client,
- search,
- page,
- perPage,
- );
-
- const pageCount = count ? Math.ceil(count / perPage) : 0;
-
- return (
-
-
Manage Organizations
-
-
-
-
-
-
-
-
-
- );
-}
-
-export default AdminGuard(OrganizationsAdminPage);
diff --git a/apps/web/app/admin/organizations/queries.ts b/apps/web/app/admin/organizations/queries.ts
deleted file mode 100644
index b96f79544..000000000
--- a/apps/web/app/admin/organizations/queries.ts
+++ /dev/null
@@ -1,131 +0,0 @@
-import type { SupabaseClient } from '@supabase/supabase-js';
-
-import type { Database } from '@/database.types';
-import { MEMBERSHIPS_TABLE, ORGANIZATIONS_TABLE } from '@/lib/db-tables';
-import type { UserOrganizationData } from '@/lib/organizations/database/queries';
-import type MembershipRole from '@/lib/organizations/types/membership-role';
-
-type Client = SupabaseClient;
-
-export async function getOrganizations(
- client: Client,
- search: string,
- page = 1,
- perPage = 20,
-) {
- const startOffset = (page - 1) * perPage;
- const endOffset = startOffset - 1 + perPage;
-
- let query = client.from(ORGANIZATIONS_TABLE).select<
- string,
- UserOrganizationData['organization'] & {
- memberships: {
- userId: string;
- role: MembershipRole;
- code: string;
- }[];
- }
- >(
- `
- id,
- uuid,
- name,
- logoURL: logo_url,
- memberships (
- userId: user_id,
- role,
- code
- ),
- subscription: organizations_subscriptions (
- customerId: customer_id,
- data: subscription_id (
- id,
- status,
- currency,
- interval,
- cancelAtPeriodEnd: cancel_at_period_end,
- intervalCount: interval_count,
- priceId: price_id,
- createdAt: created_at,
- periodStartsAt: period_starts_at,
- periodEndsAt: period_ends_at,
- trialStartsAt: trial_starts_at,
- trialEndsAt: trial_ends_at
- )
- )`,
- {
- count: 'exact',
- },
- );
-
- if (search) {
- query = query.ilike('name', `%${search}%`);
- }
-
- const {
- data: organizations,
- count,
- error,
- } = await query.range(startOffset, endOffset);
-
- if (error) {
- throw error;
- }
-
- return {
- organizations,
- count,
- };
-}
-
-export async function getMembershipsByOrganizationUid(
- client: Client,
- params: {
- uid: string;
- page: number;
- perPage: number;
- },
-) {
- const startOffset = (params.page - 1) * params.perPage;
- const endOffset = startOffset + params.perPage;
-
- const { data, error, count } = await client
- .from(MEMBERSHIPS_TABLE)
- .select<
- string,
- {
- id: number;
- role: MembershipRole;
- user: {
- id: string;
- displayName: string;
- photoURL: string;
- };
- }
- >(
- `
- id,
- role,
- user: user_id (
- id,
- displayName: display_name,
- photoURL: photo_url
- ),
- organization: organization_id !inner (
- id,
- uuid
- )`,
- {
- count: 'exact',
- },
- )
- .eq('organization.uuid', params.uid)
- .is('code', null)
- .range(startOffset, endOffset);
-
- if (error) {
- throw error;
- }
-
- return { data, count };
-}
diff --git a/apps/web/app/admin/page.tsx b/apps/web/app/admin/page.tsx
deleted file mode 100644
index 85c73b695..000000000
--- a/apps/web/app/admin/page.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { PageBody } from '@/components/app/Page';
-import appConfig from '@/config/app.config';
-
-import getSupabaseServerComponentClient from '@packages/supabase/server-component-client';
-
-import AdminDashboard from '../../packages/admin/components/AdminDashboard';
-import AdminGuard from '../../packages/admin/components/AdminGuard';
-import AdminHeader from '../../packages/admin/components/AdminHeader';
-
-export const metadata = {
- title: `Admin | ${appConfig.name}`,
-};
-
-async function AdminPage() {
- const data = await loadData();
-
- return (
-
- );
-}
-
-export default AdminGuard(AdminPage);
-
-async function loadData() {
- const client = getSupabaseServerComponentClient({ admin: true });
-
- const { count: usersCount } = await client.from('users').select('*', {
- count: 'exact',
- head: true,
- });
-
- const { count: organizationsCount } = await client
- .from('organizations')
- .select('*', {
- count: 'exact',
- head: true,
- });
-
- const { count: activeSubscriptions } = await client
- .from('subscriptions')
- .select(`*`, {
- count: 'exact',
- head: true,
- })
- .eq('status', 'active');
-
- const { count: trialSubscriptions } = await client
- .from('subscriptions')
- .select(`*`, {
- count: 'exact',
- head: true,
- })
- .eq('status', 'trialing');
-
- return {
- usersCount: usersCount || 0,
- organizationsCount: organizationsCount || 0,
- activeSubscriptions: activeSubscriptions || 0,
- trialSubscriptions: trialSubscriptions || 0,
- };
-}
diff --git a/apps/web/app/admin/users/@modal/[uid]/actions.server.ts b/apps/web/app/admin/users/@modal/[uid]/actions.server.ts
deleted file mode 100644
index 2b98d771c..000000000
--- a/apps/web/app/admin/users/@modal/[uid]/actions.server.ts
+++ /dev/null
@@ -1,127 +0,0 @@
-'use server';
-
-import { revalidatePath } from 'next/cache';
-import { redirect } from 'next/navigation';
-
-import { Logger } from '@kit/shared/logger';
-import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
-
-import { withAdminSession } from '~/admin/lib/actions-utils';
-
-const getClient = () => getSupabaseServerActionClient({ admin: true });
-
-export const banUser = withAdminSession(async ({ userId }) => {
- await setBanDuration(userId, `876600h`);
-});
-
-export const reactivateUser = withAdminSession(async ({ userId }) => {
- await setBanDuration(userId, `none`);
-});
-
-export const impersonateUser = withAdminSession(async ({ userId }) => {
- await assertUserIsNotCurrentSuperAdmin(userId);
-
- const client = getClient();
-
- const {
- data: { user },
- error,
- } = await client.auth.admin.getUserById(userId);
-
- if (error || !user) {
- throw new Error(`Error fetching user`);
- }
-
- const email = user.email;
-
- if (!email) {
- throw new Error(`User has no email. Cannot impersonate`);
- }
-
- const { error: linkError, data } = await getClient().auth.admin.generateLink({
- type: 'magiclink',
- email,
- options: {
- redirectTo: `/`,
- },
- });
-
- if (linkError || !data) {
- throw new Error(`Error generating magic link`);
- }
-
- const response = await fetch(data.properties?.action_link, {
- method: 'GET',
- redirect: 'manual',
- });
-
- const location = response.headers.get('Location');
-
- if (!location) {
- throw new Error(`Error generating magic link. Location header not found`);
- }
-
- const hash = new URL(location).hash.substring(1);
- const query = new URLSearchParams(hash);
- const accessToken = query.get('access_token');
- const refreshToken = query.get('refresh_token');
-
- if (!accessToken || !refreshToken) {
- throw new Error(
- `Error generating magic link. Tokens not found in URL hash.`,
- );
- }
-
- return {
- accessToken,
- refreshToken,
- };
-});
-
-export const deleteUserAction = withAdminSession(
- async ({ userId }: { userId: string; csrfToken: string }) => {
- await assertUserIsNotCurrentSuperAdmin(userId);
-
- Logger.info({ userId }, `Admin requested to delete user account`);
-
- // we don't want to send an email to the user
- const sendEmail = false;
-
- await deleteUser({
- client: getClient(),
- userId,
- sendEmail,
- });
-
- revalidatePath('/admin/users', 'page');
-
- Logger.info({ userId }, `User account deleted`);
-
- redirect('/admin/users');
- },
-);
-
-async function setBanDuration(userId: string, banDuration: string) {
- await assertUserIsNotCurrentSuperAdmin(userId);
-
- await getClient().auth.admin.updateUserById(userId, {
- ban_duration: banDuration,
- });
-
- revalidatePath('/admin/users');
-}
-
-async function assertUserIsNotCurrentSuperAdmin(targetUserId: string) {
- const { data: user } = await getSupabaseServerActionClient().auth.getUser();
- const currentUserId = user.user?.id;
-
- if (!currentUserId) {
- throw new Error(`Error fetching user`);
- }
-
- if (currentUserId === targetUserId) {
- throw new Error(
- `You cannot perform a destructive action on your own account as a Super Admin`,
- );
- }
-}
diff --git a/apps/web/app/admin/users/@modal/[uid]/ban/page.tsx b/apps/web/app/admin/users/@modal/[uid]/ban/page.tsx
deleted file mode 100644
index 7c5ab37a1..000000000
--- a/apps/web/app/admin/users/@modal/[uid]/ban/page.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { use } from 'react';
-
-import getSupabaseServerComponentClient from '@packages/supabase/server-component-client';
-
-import AdminGuard from '../../../../../../packages/admin/components/AdminGuard';
-import BanUserModal from '../components/BanUserModal';
-
-interface Params {
- params: {
- uid: string;
- };
-}
-
-function BanUserModalPage({ params }: Params) {
- const client = getSupabaseServerComponentClient({ admin: true });
- const { data, error } = use(client.auth.admin.getUserById(params.uid));
-
- if (!data || error) {
- throw new Error(`User not found`);
- }
-
- const user = data.user;
- const isBanned = 'banned_until' in user && user.banned_until !== 'none';
-
- if (isBanned) {
- throw new Error(`The user is already banned`);
- }
-
- return ;
-}
-
-export default AdminGuard(BanUserModalPage);
diff --git a/apps/web/app/admin/users/@modal/[uid]/components/BanUserModal.tsx b/apps/web/app/admin/users/@modal/[uid]/components/BanUserModal.tsx
deleted file mode 100644
index 676cd9bf5..000000000
--- a/apps/web/app/admin/users/@modal/[uid]/components/BanUserModal.tsx
+++ /dev/null
@@ -1,111 +0,0 @@
-'use client';
-
-import { useState } from 'react';
-
-import { useFormStatus } from 'react-dom';
-
-import { useRouter } from 'next/navigation';
-
-import type { User } from '@supabase/gotrue-js';
-
-import ErrorBoundary from '@/components/app/ErrorBoundary';
-
-import useCsrfToken from '@kit/hooks/use-csrf-token';
-import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
-import { Button } from '@kit/ui/button';
-import {
- Dialog,
- DialogContent,
- DialogHeader,
- DialogTitle,
-} from '@kit/ui/dialog';
-import { Input } from '@kit/ui/input';
-import { Label } from '@kit/ui/label';
-
-import { banUser } from '../actions.server';
-
-function BanUserModal({
- user,
-}: React.PropsWithChildren<{
- user: User;
-}>) {
- const router = useRouter();
- const [isOpen, setIsOpen] = useState(true);
- const csrfToken = useCsrfToken();
- const displayText = user.email ?? user.phone ?? '';
-
- const onDismiss = () => {
- router.back();
-
- setIsOpen(false);
- };
-
- const onConfirm = async () => {
- await banUser({
- userId: user.id,
- csrfToken,
- });
-
- onDismiss();
- };
-
- return (
-
- );
-}
-
-function SubmitButton() {
- const { pending } = useFormStatus();
-
- return (
-
- );
-}
-
-export default BanUserModal;
-
-function BanErrorAlert() {
- return (
-
- There was an error banning this user.
-
- Check the logs for more information.
-
- );
-}
diff --git a/apps/web/app/admin/users/@modal/[uid]/components/DeleteUserModal.tsx b/apps/web/app/admin/users/@modal/[uid]/components/DeleteUserModal.tsx
deleted file mode 100644
index ca0e1d0f4..000000000
--- a/apps/web/app/admin/users/@modal/[uid]/components/DeleteUserModal.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-'use client';
-
-import { useState, useTransition } from 'react';
-
-import { useRouter } from 'next/navigation';
-
-import type { User } from '@supabase/gotrue-js';
-
-import useCsrfToken from '@kit/hooks/use-csrf-token';
-import { Button } from '@kit/ui/button';
-import {
- Dialog,
- DialogContent,
- DialogHeader,
- DialogTitle,
-} from '@kit/ui/dialog';
-import { Input } from '@kit/ui/input';
-import { Label } from '@kit/ui/label';
-
-import { deleteUserAction } from '../actions.server';
-
-function DeleteUserModal({
- user,
-}: React.PropsWithChildren<{
- user: User;
-}>) {
- const router = useRouter();
- const [isOpen, setIsOpen] = useState(true);
- const [pending, startTransition] = useTransition();
- const csrfToken = useCsrfToken();
- const displayText = user.email ?? user.phone ?? '';
-
- const onDismiss = () => {
- router.back();
-
- setIsOpen(false);
- };
-
- const onConfirm = () => {
- startTransition(async () => {
- await deleteUserAction({
- userId: user.id,
- csrfToken,
- });
-
- onDismiss();
- });
- };
-
- return (
-
- );
-}
-
-export default DeleteUserModal;
diff --git a/apps/web/app/admin/users/@modal/[uid]/components/ImpersonateUserAuthSetter.tsx b/apps/web/app/admin/users/@modal/[uid]/components/ImpersonateUserAuthSetter.tsx
deleted file mode 100644
index 70cfd8cfc..000000000
--- a/apps/web/app/admin/users/@modal/[uid]/components/ImpersonateUserAuthSetter.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-'use client';
-
-import { useEffect } from 'react';
-
-import { useRouter } from 'next/navigation';
-
-import Spinner from '@/components/app/Spinner';
-
-import useSupabase from '@kit/hooks/use-supabase';
-
-function ImpersonateUserAuthSetter({
- tokens,
-}: React.PropsWithChildren<{
- tokens: {
- accessToken: string;
- refreshToken: string;
- };
-}>) {
- const supabase = useSupabase();
- const router = useRouter();
-
- useEffect(() => {
- async function setAuth() {
- await supabase.auth.setSession({
- refresh_token: tokens.refreshToken,
- access_token: tokens.accessToken,
- });
-
- router.push('/dashboard');
- }
-
- void setAuth();
- }, [router, tokens, supabase.auth]);
-
- return (
-
-
-
-
-
-
Setting up your session...
-
-
-
- );
-}
-
-export default ImpersonateUserAuthSetter;
diff --git a/apps/web/app/admin/users/@modal/[uid]/components/ImpersonateUserConfirmationModal.tsx b/apps/web/app/admin/users/@modal/[uid]/components/ImpersonateUserConfirmationModal.tsx
deleted file mode 100644
index 4a2713ce3..000000000
--- a/apps/web/app/admin/users/@modal/[uid]/components/ImpersonateUserConfirmationModal.tsx
+++ /dev/null
@@ -1,128 +0,0 @@
-'use client';
-
-import { useState, useTransition } from 'react';
-
-import { useRouter } from 'next/navigation';
-
-import type { User } from '@supabase/gotrue-js';
-
-import If from '@/components/app/If';
-import LoadingOverlay from '@/components/app/LoadingOverlay';
-
-import useCsrfToken from '@kit/hooks/use-csrf-token';
-import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
-import { Button } from '@kit/ui/button';
-import {
- Dialog,
- DialogContent,
- DialogHeader,
- DialogTitle,
-} from '@kit/ui/dialog';
-
-import { impersonateUser } from '../actions.server';
-import ImpersonateUserAuthSetter from '../components/ImpersonateUserAuthSetter';
-
-function ImpersonateUserConfirmationModal({
- user,
-}: React.PropsWithChildren<{
- user: User;
-}>) {
- const router = useRouter();
- const [isOpen, setIsOpen] = useState(true);
- const [pending, startTransition] = useTransition();
- const csrfToken = useCsrfToken();
- const [error, setError] = useState();
-
- const [tokens, setTokens] = useState<{
- accessToken: string;
- refreshToken: string;
- }>();
-
- const displayText = user.email ?? user.phone ?? '';
-
- const onDismiss = () => {
- router.back();
-
- setIsOpen(false);
- };
-
- const onConfirm = () => {
- startTransition(async () => {
- try {
- const response = await impersonateUser({
- userId: user.id,
- csrfToken,
- });
-
- setTokens(response);
- } catch (e) {
- setError(true);
- }
- });
- };
-
- return (
-
- );
-}
-
-export default ImpersonateUserConfirmationModal;
diff --git a/apps/web/app/admin/users/@modal/[uid]/components/ReactivateUserModal.tsx b/apps/web/app/admin/users/@modal/[uid]/components/ReactivateUserModal.tsx
deleted file mode 100644
index 10611be1a..000000000
--- a/apps/web/app/admin/users/@modal/[uid]/components/ReactivateUserModal.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-'use client';
-
-import { useState, useTransition } from 'react';
-
-import { useRouter } from 'next/navigation';
-
-import type { User } from '@supabase/gotrue-js';
-
-import useCsrfToken from '@kit/hooks/use-csrf-token';
-import { Button } from '@kit/ui/button';
-import {
- Dialog,
- DialogContent,
- DialogHeader,
- DialogTitle,
-} from '@kit/ui/dialog';
-
-import { reactivateUser } from '../actions.server';
-
-function ReactivateUserModal({
- user,
-}: React.PropsWithChildren<{
- user: User;
-}>) {
- const router = useRouter();
- const [isOpen, setIsOpen] = useState(true);
- const [pending, startTransition] = useTransition();
- const csrfToken = useCsrfToken();
- const displayText = user.email ?? user.phone ?? '';
-
- const onDismiss = () => {
- router.back();
-
- setIsOpen(false);
- };
-
- const onConfirm = () => {
- startTransition(async () => {
- await reactivateUser({
- userId: user.id,
- csrfToken,
- });
-
- onDismiss();
- });
- };
-
- return (
-
- );
-}
-
-export default ReactivateUserModal;
diff --git a/apps/web/app/admin/users/@modal/[uid]/delete/page.tsx b/apps/web/app/admin/users/@modal/[uid]/delete/page.tsx
deleted file mode 100644
index 6c3f04396..000000000
--- a/apps/web/app/admin/users/@modal/[uid]/delete/page.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { use } from 'react';
-
-import getSupabaseServerComponentClient from '@packages/supabase/server-component-client';
-
-import AdminGuard from '../../../../../../packages/admin/components/AdminGuard';
-import DeleteUserModal from '../components/DeleteUserModal';
-
-interface Params {
- params: {
- uid: string;
- };
-}
-
-function DeleteUserModalPage({ params }: Params) {
- const client = getSupabaseServerComponentClient({ admin: true });
- const { data, error } = use(client.auth.admin.getUserById(params.uid));
-
- if (!data || error) {
- throw new Error(`User not found`);
- }
-
- return ;
-}
-
-export default AdminGuard(DeleteUserModalPage);
diff --git a/apps/web/app/admin/users/@modal/[uid]/impersonate/page.tsx b/apps/web/app/admin/users/@modal/[uid]/impersonate/page.tsx
deleted file mode 100644
index dcd0856ff..000000000
--- a/apps/web/app/admin/users/@modal/[uid]/impersonate/page.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { use } from 'react';
-
-import getSupabaseServerComponentClient from '@packages/supabase/server-component-client';
-
-import AdminGuard from '../../../../../../packages/admin/components/AdminGuard';
-import ImpersonateUserConfirmationModal from '../components/ImpersonateUserConfirmationModal';
-
-interface Params {
- params: {
- uid: string;
- };
-}
-
-function ImpersonateUserModalPage({ params }: Params) {
- const client = getSupabaseServerComponentClient({ admin: true });
- const { data, error } = use(client.auth.admin.getUserById(params.uid));
-
- if (!data || error) {
- throw new Error(`User not found`);
- }
-
- return ;
-}
-
-export default AdminGuard(ImpersonateUserModalPage);
diff --git a/apps/web/app/admin/users/@modal/[uid]/reactivate/page.tsx b/apps/web/app/admin/users/@modal/[uid]/reactivate/page.tsx
deleted file mode 100644
index 38b0d1149..000000000
--- a/apps/web/app/admin/users/@modal/[uid]/reactivate/page.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { use } from 'react';
-
-import { redirect } from 'next/navigation';
-
-import getSupabaseServerComponentClient from '@packages/supabase/server-component-client';
-
-import AdminGuard from '../../../../../../packages/admin/components/AdminGuard';
-import ReactivateUserModal from '../components/ReactivateUserModal';
-
-interface Params {
- params: {
- uid: string;
- };
-}
-
-function ReactivateUserModalPage({ params }: Params) {
- const client = getSupabaseServerComponentClient({ admin: true });
- const { data, error } = use(client.auth.admin.getUserById(params.uid));
-
- if (!data || error) {
- throw new Error(`User not found`);
- }
-
- const user = data.user;
- const isActive = !('banned_until' in user) || user.banned_until === 'none';
-
- if (isActive) {
- redirect(`/admin/users`);
- }
-
- return ;
-}
-
-export default AdminGuard(ReactivateUserModalPage);
diff --git a/apps/web/app/admin/users/@modal/default.tsx b/apps/web/app/admin/users/@modal/default.tsx
deleted file mode 100644
index 6ddf1b76f..000000000
--- a/apps/web/app/admin/users/@modal/default.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function Default() {
- return null;
-}
diff --git a/apps/web/app/admin/users/[uid]/components/UserActionsDropdown.tsx b/apps/web/app/admin/users/[uid]/components/UserActionsDropdown.tsx
deleted file mode 100644
index 0db1b50d6..000000000
--- a/apps/web/app/admin/users/[uid]/components/UserActionsDropdown.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-'use client';
-
-import Link from 'next/link';
-
-import If from '@/components/app/If';
-import { EllipsisVerticalIcon } from 'lucide-react';
-
-import { Button } from '@kit/ui/button';
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from '@kit/ui/dropdown-menu';
-
-function UserActionsDropdown({
- uid,
- isBanned,
-}: React.PropsWithChildren<{
- uid: string;
- isBanned: boolean;
-}>) {
- return (
-
-
-
-
-
-
-
- Impersonate
-
-
-
-
-
- Ban
-
-
-
-
-
-
- Reactivate
-
-
-
-
-
- Delete
-
-
-
-
- );
-}
-
-export default UserActionsDropdown;
diff --git a/apps/web/app/admin/users/[uid]/page.tsx b/apps/web/app/admin/users/[uid]/page.tsx
deleted file mode 100644
index bb8c44859..000000000
--- a/apps/web/app/admin/users/[uid]/page.tsx
+++ /dev/null
@@ -1,238 +0,0 @@
-import Link from 'next/link';
-
-import { PageBody } from '@/components/app/Page';
-import configuration from '@/config/app.config';
-import type MembershipRole from '@/lib/organizations/types/membership-role';
-import { ChevronRightIcon } from 'lucide-react';
-
-import getSupabaseServerComponentClient from '@packages/supabase/server-component-client';
-
-import { Badge } from '@kit/ui/badge';
-import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
-import { Input } from '@kit/ui/input';
-import { Label } from '@kit/ui/label';
-import {
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableHeader,
- TableRow,
-} from '@kit/ui/table';
-
-import RoleBadge from '../../../(app)/[account]/account/organization/components/RoleBadge';
-import AdminGuard from '../../../../packages/admin/components/AdminGuard';
-import AdminHeader from '../../../../packages/admin/components/AdminHeader';
-import UserActionsDropdown from './components/UserActionsDropdown';
-
-interface Params {
- params: {
- uid: string;
- };
-}
-
-export const metadata = {
- title: `Manage User | ${configuration.name}`,
-};
-
-async function AdminUserPage({ params }: Params) {
- const uid = params.uid;
-
- const data = await loadData(uid);
- const { auth, user } = data;
- const displayName = user?.displayName;
- const authUser = auth?.user;
- const email = authUser?.email;
- const phone = authUser?.phone;
- const organizations = data.organizations ?? [];
-
- const isBanned = Boolean(
- authUser && 'banned_until' in authUser && authUser.banned_until !== 'none',
- );
-
- return (
-
-
Manage User
-
-
-
-
-
-
-
- User Details
-
-
-
-
-
-
-
-
-
- {isBanned ? (
- Banned
- ) : (
- Active
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
- Organizations
-
-
-
-
-
-
- Organization ID
- UUID
- Organization
- Role
-
-
-
-
- {organizations.map((membership) => {
- const organization = membership.organization;
- const href = `/admin/organizations/${organization.uuid}/members`;
-
- return (
-
- {organization.id}
- {organization.uuid}
-
-
-
- {organization.name}
-
-
-
-
-
-
-
-
-
- );
- })}
-
-
-
-
-
-
-
- );
-}
-
-export default AdminGuard(AdminUserPage);
-
-async function loadData(uid: string) {
- const client = getSupabaseServerComponentClient({ admin: true });
- const authUser = client.auth.admin.getUserById(uid);
-
- const userData = client
- .from('users')
- .select(
- `
- id,
- displayName: display_name,
- photoURL: photo_url,
- onboarded
- `,
- )
- .eq('id', uid)
- .single();
-
- const organizationsQuery = client
- .from('memberships')
- .select<
- string,
- {
- id: number;
- role: MembershipRole;
- organization: {
- id: number;
- uuid: string;
- name: string;
- };
- }
- >(
- `
- id,
- role,
- organization: organization_id !inner (
- id,
- uuid,
- name
- )
- `,
- )
- .eq('user_id', uid);
-
- const [auth, user, organizations] = await Promise.all([
- authUser,
- userData,
- organizationsQuery,
- ]);
-
- return {
- auth: auth.data,
- user: user.data,
- organizations: organizations.data,
- };
-}
-
-function Breadcrumbs(
- props: React.PropsWithChildren<{
- displayName: string;
- }>,
-) {
- return (
-
- Admin
-
- Users
-
- {props.displayName}
-
- );
-}
diff --git a/apps/web/app/admin/users/default.tsx b/apps/web/app/admin/users/default.tsx
deleted file mode 100644
index 6ddf1b76f..000000000
--- a/apps/web/app/admin/users/default.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function Default() {
- return null;
-}
diff --git a/apps/web/app/admin/users/error.tsx b/apps/web/app/admin/users/error.tsx
deleted file mode 100644
index 5e83f1d7a..000000000
--- a/apps/web/app/admin/users/error.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-'use client';
-
-import { PageBody } from '@/components/app/Page';
-
-import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
-
-function UsersAdminPageError() {
- return (
-
-
- Could not load users
-
- There was an error loading the users. Please check your console
- errors.
-
-
-
- );
-}
-
-export default UsersAdminPageError;
diff --git a/apps/web/app/admin/users/layout.tsx b/apps/web/app/admin/users/layout.tsx
deleted file mode 100644
index 28dd86068..000000000
--- a/apps/web/app/admin/users/layout.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-function UserLayout(
- props: React.PropsWithChildren<{
- modal: React.ReactNode;
- }>,
-) {
- return (
- <>
- {props.modal}
- {props.children}
- >
- );
-}
-
-export default UserLayout;
diff --git a/apps/web/app/admin/users/page.tsx b/apps/web/app/admin/users/page.tsx
deleted file mode 100644
index ce7401a26..000000000
--- a/apps/web/app/admin/users/page.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import { PageBody } from '@/components/app/Page';
-import appConfig from '@/config/app.config';
-
-import getSupabaseServerComponentClient from '@packages/supabase/server-component-client';
-
-import type UserData from '@kit/session/types/user-data';
-
-import AdminGuard from '../../../packages/admin/components/AdminGuard';
-import AdminHeader from '../../../packages/admin/components/AdminHeader';
-import getPageFromQueryParams from '../utils/get-page-from-query-param';
-import { getUsers } from './queries';
-
-interface UsersAdminPageProps {
- searchParams: {
- page?: string;
- };
-}
-
-export const metadata = {
- title: `Users | ${appConfig.name}`,
-};
-
-async function UsersAdminPage({ searchParams }: UsersAdminPageProps) {
- const page = getPageFromQueryParams(searchParams.page);
- const perPage = 1;
- const { users, total } = await loadUsers(page, perPage);
- const pageCount = Math.ceil(total / perPage);
-
- return (
-
- );
-}
-
-export default AdminGuard(UsersAdminPage);
-
-async function loadAuthUsers(page = 1, perPage = 20) {
- const client = getSupabaseServerComponentClient({ admin: true });
-
- const response = await client.auth.admin.listUsers({
- page,
- perPage,
- });
-
- if (response.error) {
- throw response.error;
- }
-
- return response.data;
-}
-
-async function loadUsers(page = 1, perPage = 20) {
- const { users: authUsers, total } = await loadAuthUsers(page, perPage);
-
- const ids = authUsers.map((user) => user.id);
- const usersData = await getUsers(ids);
-
- const users = authUsers
- .map((user) => {
- const data = usersData.find((u) => u.id === user.id) as UserData;
-
- const banDuration =
- 'banned_until' in user ? (user.banned_until as string) : 'none';
-
- return {
- id: user.id,
- email: user.email,
- phone: user.phone,
- createdAt: user.created_at,
- updatedAt: user.updated_at,
- lastSignInAt: user.last_sign_in_at,
- banDuration,
- data,
- };
- })
- .filter(Boolean);
-
- return {
- total,
- users,
- };
-}
diff --git a/apps/web/app/admin/users/queries.ts b/apps/web/app/admin/users/queries.ts
deleted file mode 100644
index 0acb10f24..000000000
--- a/apps/web/app/admin/users/queries.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { USERS_TABLE } from '@/lib/db-tables';
-
-import getSupabaseServerComponentClient from '@packages/supabase/server-component-client';
-
-export async function getUsers(ids: string[]) {
- const client = getSupabaseServerComponentClient({ admin: true });
-
- const { data: users, error } = await client
- .from(USERS_TABLE)
- .select(
- `
- id,
- photoURL: photo_url,
- displayName: display_name,
- onboarded
- `,
- )
- .in('id', ids);
-
- if (error) {
- throw error;
- }
-
- return users;
-}
diff --git a/apps/web/app/admin/utils/get-page-from-query-param.ts b/apps/web/app/admin/utils/get-page-from-query-param.ts
deleted file mode 100644
index 3a93a1291..000000000
--- a/apps/web/app/admin/utils/get-page-from-query-param.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * Get page from query params
- * @name getPageFromQueryParams
- * @param pageParam
- */
-function getPageFromQueryParams(pageParam: string | undefined) {
- const page = pageParam ? parseInt(pageParam) : 1;
-
- if (Number.isNaN(page) || page <= 0) {
- return 1;
- }
-
- return page;
-}
-
-export default getPageFromQueryParams;
diff --git a/apps/web/app/admin/utils/is-user-super-admin.ts b/apps/web/app/admin/utils/is-user-super-admin.ts
deleted file mode 100644
index 53f587a45..000000000
--- a/apps/web/app/admin/utils/is-user-super-admin.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import type { SupabaseClient } from '@supabase/supabase-js';
-
-import { Database } from '@kit/supabase/database';
-
-/**
- * @name ENFORCE_MFA
- * @description Set this constant to true if you want the SuperAdmin user to
- * sign in using MFA when accessing the Admin page
- */
-const ENFORCE_MFA = false;
-
-/**
- * @name isUserSuperAdmin
- * @description Checks if the current user is an admin by checking the
- * user_metadata.role field in Supabase Auth is set to a SuperAdmin role.
- */
-const isUserSuperAdmin = async (params: {
- client: SupabaseClient;
- enforceMfa?: boolean;
-}) => {
- const enforceMfa = params.enforceMfa ?? ENFORCE_MFA;
- const { data, error } = await params.client.auth.getUser();
-
- if (error) {
- return false;
- }
-
- // If we enforce MFA, we need to check that the user is MFA authenticated.
- if (enforceMfa) {
- const isMfaAuthenticated = await verifyIsMultiFactorAuthenticated(
- params.client,
- );
-
- if (!isMfaAuthenticated) {
- return false;
- }
- }
-
- const adminMetadata = data.user?.app_metadata;
- const role = adminMetadata?.role;
-
- return role === 'super-admin';
-};
-
-export default isUserSuperAdmin;
-
-async function verifyIsMultiFactorAuthenticated(client: SupabaseClient) {
- const { data, error } =
- await client.auth.mfa.getAuthenticatorAssuranceLevel();
-
- if (error || !data) {
- return false;
- }
-
- return data.currentLevel === 'aal2';
-}
diff --git a/apps/web/app/error.tsx b/apps/web/app/error.tsx
index b7af07fe5..613518207 100644
--- a/apps/web/app/error.tsx
+++ b/apps/web/app/error.tsx
@@ -8,7 +8,7 @@ import { Button } from '@kit/ui/button';
import { Heading } from '@kit/ui/heading';
import { Trans } from '@kit/ui/trans';
-import { SiteHeader } from '~/(marketing)/components/site-header';
+import { SiteHeader } from '~/(marketing)/_components/site-header';
const ErrorPage = () => {
return (
diff --git a/apps/web/app/join/_components/ExistingUserInviteForm.tsx b/apps/web/app/join/_components/ExistingUserInviteForm.tsx
deleted file mode 100644
index ab4348584..000000000
--- a/apps/web/app/join/_components/ExistingUserInviteForm.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-'use client';
-
-import { useCallback, useTransition } from 'react';
-
-import type { Session } from '@supabase/gotrue-js';
-
-import useRefreshRoute from '@kit/shared/hooks/use-refresh-route';
-import { useSignOut } from '@kit/supabase/hooks/use-sign-out';
-import { Button } from '@kit/ui/button';
-import { Trans } from '@kit/ui/trans';
-
-function ExistingUserInviteForm(
- props: React.PropsWithChildren<{
- session: Session;
- code: string;
- }>,
-) {
- const signOut = useSignOut();
- const refresh = useRefreshRoute();
- const [isSubmitting, startTransition] = useTransition();
-
- const onSignOut = useCallback(async () => {
- await signOut.mutateAsync();
- refresh();
- }, [refresh, signOut]);
-
- const onInviteAccepted = useCallback(() => {
- return startTransition(async () => {
- await acceptInviteAction({
- code: props.code,
- });
- });
- }, [props.code, startTransition]);
-
- return (
- <>
-
-
- }}
- />
-
-
-
-
-
-
- >
- );
-}
-
-export default ExistingUserInviteForm;
diff --git a/apps/web/app/join/_components/NewUserInviteForm.tsx b/apps/web/app/join/_components/NewUserInviteForm.tsx
deleted file mode 100644
index 0a4acffbf..000000000
--- a/apps/web/app/join/_components/NewUserInviteForm.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-'use client';
-
-import { useCallback, useState, useTransition } from 'react';
-
-import { EmailOtpContainer } from '@kit/auth/src/components/email-otp-container';
-import { OauthProviders } from '@kit/auth/src/components/oauth-providers';
-import { PasswordSignInContainer } from '@kit/auth/src/components/password-sign-in-container';
-import { EmailPasswordSignUpContainer } from '@kit/auth/src/components/password-sign-up-container';
-import { isBrowser } from '@kit/shared/utils';
-import { Button } from '@kit/ui/button';
-import { If } from '@kit/ui/if';
-import { LoadingOverlay } from '@kit/ui/loading-overlay';
-import { Trans } from '@kit/ui/trans';
-
-import authConfig from '~/config/auth.config';
-
-enum Mode {
- SignUp,
- SignIn,
-}
-
-function NewUserInviteForm(
- props: React.PropsWithChildren<{
- code: string;
- }>,
-) {
- const [mode, setMode] = useState(Mode.SignUp);
- const [isSubmitting, startTransition] = useTransition();
- const oAuthReturnUrl = isBrowser() ? window.location.pathname : '';
-
- const onInviteAccepted = useCallback(
- async (userId?: string) => {
- startTransition(async () => {
- await acceptInviteAction({
- code: props.code,
- userId,
- });
- });
- },
- [props.code],
- );
-
- return (
- <>
-
-
- Accepting invite. Please wait...
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-}
-
-export default NewUserInviteForm;
diff --git a/apps/web/app/join/page.tsx b/apps/web/app/join/page.tsx
index f3c9c364b..03dbbf067 100644
--- a/apps/web/app/join/page.tsx
+++ b/apps/web/app/join/page.tsx
@@ -1,17 +1,10 @@
-import { headers } from 'next/headers';
import { notFound } from 'next/navigation';
-import type { SupabaseClient } from '@supabase/supabase-js';
-
-import { Logger } from '@kit/shared/logger';
-import { Database } from '@kit/supabase/database';
import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client';
import { Heading } from '@kit/ui/heading';
import { If } from '@kit/ui/if';
import { Trans } from '@kit/ui/trans';
-import ExistingUserInviteForm from '~/join/_components/ExistingUserInviteForm';
-import NewUserInviteForm from '~/join/_components/NewUserInviteForm';
import { withI18n } from '~/lib/i18n/with-i18n';
interface Context {
@@ -28,12 +21,10 @@ async function JoinTeamAccountPage({ searchParams }: Context) {
const token = searchParams.invite_token;
const data = await getInviteDataFromInviteToken(token);
- if (!data.membership) {
+ if (!data) {
notFound();
}
- const organization = data.membership.organization;
-
return (
<>
@@ -62,70 +53,26 @@ async function JoinTeamAccountPage({ searchParams }: Context) {
}
-
>
);
}
export default withI18n(JoinTeamAccountPage);
-async function getInviteDataFromInviteToken(code: string) {
- const client = getSupabaseServerComponentClient();
-
+async function getInviteDataFromInviteToken(token: string) {
// we use an admin client to be able to read the pending membership
// without having to be logged in
const adminClient = getSupabaseServerComponentClient({ admin: true });
- const { data: membership, error } = await getInvite(adminClient, code);
+ const { data: invitation, error } = await adminClient
+ .from('invitations')
+ .select('*')
+ .eq('invite_token', token)
+ .single();
- // if the invite wasn't found, it's 404
- if (error) {
- Logger.warn(
- {
- code,
- error,
- },
- `User navigated to invite page, but it wasn't found. Redirecting to home page...`,
- );
-
- notFound();
+ if (!invitation ?? error) {
+ return null;
}
- const { data: userSession } = await client.auth.getSession();
- const session = userSession?.session;
- const csrfToken = headers().get('x-csrf-token');
-
- return {
- csrfToken,
- session,
- membership,
- code,
- };
-}
-
-function getInvite(adminClient: SupabaseClient