diff --git a/.gitignore b/.gitignore
index 95d9bf88..fef3571d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,7 +11,6 @@ node_modules
# next.js
/.next/
/out/
-/prisma/
/src/generated/
# production
diff --git a/db/mysql/migrations/11_add_segment/migration.sql b/db/mysql/migrations/11_add_segment/migration.sql
deleted file mode 100644
index c79e916d..00000000
--- a/db/mysql/migrations/11_add_segment/migration.sql
+++ /dev/null
@@ -1,14 +0,0 @@
--- CreateTable
-CREATE TABLE `segment` (
- `segment_id` VARCHAR(36) NOT NULL,
- `website_id` VARCHAR(36) NOT NULL,
- `type` VARCHAR(200) NOT NULL,
- `name` VARCHAR(200) NOT NULL,
- `parameters` JSON NOT NULL,
- `created_at` TIMESTAMP(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
- `updated_at` TIMESTAMP(0) NULL,
-
- UNIQUE INDEX `segment_segment_id_key`(`segment_id`),
- INDEX `segment_website_id_idx`(`website_id`),
- PRIMARY KEY (`segment_id`)
-) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
diff --git a/db/mysql/migrations/12_update_report_parameter/migration.sql b/db/mysql/migrations/12_update_report_parameter/migration.sql
deleted file mode 100644
index f6a99c3f..00000000
--- a/db/mysql/migrations/12_update_report_parameter/migration.sql
+++ /dev/null
@@ -1,2 +0,0 @@
--- AlterTable
-ALTER TABLE `report` MODIFY `parameters` JSON NOT NULL;
diff --git a/db/mysql/migrations/13_add_revenue/migration.sql b/db/mysql/migrations/13_add_revenue/migration.sql
deleted file mode 100644
index 96115a33..00000000
--- a/db/mysql/migrations/13_add_revenue/migration.sql
+++ /dev/null
@@ -1,18 +0,0 @@
--- CreateTable
-CREATE TABLE `revenue` (
- `revenue_id` VARCHAR(36) NOT NULL,
- `website_id` VARCHAR(36) NOT NULL,
- `session_id` VARCHAR(36) NOT NULL,
- `event_id` VARCHAR(36) NOT NULL,
- `event_name` VARCHAR(50) NOT NULL,
- `currency` VARCHAR(100) NOT NULL,
- `revenue` DECIMAL(19, 4) NULL,
- `created_at` TIMESTAMP(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
-
- UNIQUE INDEX `revenue_revenue_id_key`(`revenue_id`),
- INDEX `revenue_website_id_idx`(`website_id`),
- INDEX `revenue_session_id_idx`(`session_id`),
- INDEX `revenue_website_id_created_at_idx`(`website_id`, `created_at`),
- INDEX `revenue_website_id_session_id_created_at_idx`(`website_id`, `session_id`, `created_at`),
- PRIMARY KEY (`revenue_id`)
-) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
diff --git a/scripts/data-migrations/convert-utm-clid-columns.sql b/db/postgresql/data-migrations/convert-utm-clid-columns.sql
similarity index 100%
rename from scripts/data-migrations/convert-utm-clid-columns.sql
rename to db/postgresql/data-migrations/convert-utm-clid-columns.sql
diff --git a/scripts/data-migrations/populate-revenue-table.sql b/db/postgresql/data-migrations/populate-revenue-table.sql
similarity index 100%
rename from scripts/data-migrations/populate-revenue-table.sql
rename to db/postgresql/data-migrations/populate-revenue-table.sql
diff --git a/package.json b/package.json
index 425d48b1..9d062905 100644
--- a/package.json
+++ b/package.json
@@ -13,10 +13,10 @@
"scripts": {
"dev": "next dev",
"dev-turbo": "next dev -p 3001 --turbopack",
- "build": "npm-run-all check-env build-db check-db build-tracker build-geo build-app",
- "build-turbo": "npm-run-all check-env build-db check-db build-tracker build-geo build-app-turbo",
+ "build": "npm-run-all check-env build-db-client check-db build-tracker build-geo build-app",
+ "build-turbo": "npm-run-all check-env build-db-client check-db build-tracker build-geo build-app-turbo",
"start": "next start",
- "build-docker": "npm-run-all build-db build-tracker build-geo build-app",
+ "build-docker": "npm-run-all build-db-client build-tracker build-geo build-app",
"start-docker": "npm-run-all check-db update-tracker set-routes-manifest start-server",
"start-env": "node scripts/start-env.js",
"start-server": "node server.js",
@@ -26,7 +26,6 @@
"build-components": "rollup -c rollup.components.config.js",
"build-tracker": "rollup -c rollup.tracker.config.js",
"build-prisma-client": "node scripts/build-prisma-client.js",
- "build-db": "npm-run-all copy-db-files build-db-client",
"build-lang": "npm-run-all format-lang compile-lang download-country-names download-language-names clean-lang",
"build-geo": "node scripts/build-geo.js",
"build-db-schema": "prisma db pull",
diff --git a/db/postgresql/migrations/01_init/migration.sql b/prisma/migrations/01_init/migration.sql
similarity index 100%
rename from db/postgresql/migrations/01_init/migration.sql
rename to prisma/migrations/01_init/migration.sql
diff --git a/db/postgresql/migrations/02_report_schema_session_data/migration.sql b/prisma/migrations/02_report_schema_session_data/migration.sql
similarity index 100%
rename from db/postgresql/migrations/02_report_schema_session_data/migration.sql
rename to prisma/migrations/02_report_schema_session_data/migration.sql
diff --git a/db/postgresql/migrations/03_metric_performance_index/migration.sql b/prisma/migrations/03_metric_performance_index/migration.sql
similarity index 100%
rename from db/postgresql/migrations/03_metric_performance_index/migration.sql
rename to prisma/migrations/03_metric_performance_index/migration.sql
diff --git a/db/postgresql/migrations/04_team_redesign/migration.sql b/prisma/migrations/04_team_redesign/migration.sql
similarity index 100%
rename from db/postgresql/migrations/04_team_redesign/migration.sql
rename to prisma/migrations/04_team_redesign/migration.sql
diff --git a/db/postgresql/migrations/05_add_visit_id/migration.sql b/prisma/migrations/05_add_visit_id/migration.sql
similarity index 100%
rename from db/postgresql/migrations/05_add_visit_id/migration.sql
rename to prisma/migrations/05_add_visit_id/migration.sql
diff --git a/db/postgresql/migrations/06_session_data/migration.sql b/prisma/migrations/06_session_data/migration.sql
similarity index 100%
rename from db/postgresql/migrations/06_session_data/migration.sql
rename to prisma/migrations/06_session_data/migration.sql
diff --git a/db/postgresql/migrations/07_add_tag/migration.sql b/prisma/migrations/07_add_tag/migration.sql
similarity index 100%
rename from db/postgresql/migrations/07_add_tag/migration.sql
rename to prisma/migrations/07_add_tag/migration.sql
diff --git a/db/postgresql/migrations/08_add_utm_clid/migration.sql b/prisma/migrations/08_add_utm_clid/migration.sql
similarity index 100%
rename from db/postgresql/migrations/08_add_utm_clid/migration.sql
rename to prisma/migrations/08_add_utm_clid/migration.sql
diff --git a/db/postgresql/migrations/09_update_hostname_region/migration.sql b/prisma/migrations/09_update_hostname_region/migration.sql
similarity index 100%
rename from db/postgresql/migrations/09_update_hostname_region/migration.sql
rename to prisma/migrations/09_update_hostname_region/migration.sql
diff --git a/db/postgresql/migrations/10_add_distinct_id/migration.sql b/prisma/migrations/10_add_distinct_id/migration.sql
similarity index 100%
rename from db/postgresql/migrations/10_add_distinct_id/migration.sql
rename to prisma/migrations/10_add_distinct_id/migration.sql
diff --git a/db/postgresql/migrations/11_add_segment/migration.sql b/prisma/migrations/11_add_segment/migration.sql
similarity index 100%
rename from db/postgresql/migrations/11_add_segment/migration.sql
rename to prisma/migrations/11_add_segment/migration.sql
diff --git a/db/postgresql/migrations/12_update_report_parameter/migration.sql b/prisma/migrations/12_update_report_parameter/migration.sql
similarity index 100%
rename from db/postgresql/migrations/12_update_report_parameter/migration.sql
rename to prisma/migrations/12_update_report_parameter/migration.sql
diff --git a/db/postgresql/migrations/13_add_revenue/migration.sql b/prisma/migrations/13_add_revenue/migration.sql
similarity index 100%
rename from db/postgresql/migrations/13_add_revenue/migration.sql
rename to prisma/migrations/13_add_revenue/migration.sql
diff --git a/prisma/migrations/14_add_link_and_pixel/migration.sql b/prisma/migrations/14_add_link_and_pixel/migration.sql
new file mode 100644
index 00000000..9c08fc61
--- /dev/null
+++ b/prisma/migrations/14_add_link_and_pixel/migration.sql
@@ -0,0 +1,67 @@
+-- AlterTable
+ALTER TABLE "report" ALTER COLUMN "type" SET DATA TYPE VARCHAR(50);
+
+-- AlterTable
+ALTER TABLE "revenue" ALTER COLUMN "currency" SET DATA TYPE VARCHAR(10);
+
+-- AlterTable
+ALTER TABLE "segment" ALTER COLUMN "type" SET DATA TYPE VARCHAR(50);
+
+-- CreateTable
+CREATE TABLE "link" (
+ "link_id" UUID NOT NULL,
+ "name" VARCHAR(100) NOT NULL,
+ "url" VARCHAR(500) NOT NULL,
+ "slug" VARCHAR(100) NOT NULL,
+ "user_id" UUID,
+ "team_id" UUID,
+ "created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
+ "updated_at" TIMESTAMPTZ(6),
+ "deleted_at" TIMESTAMPTZ(6),
+
+ CONSTRAINT "link_pkey" PRIMARY KEY ("link_id")
+);
+
+-- CreateTable
+CREATE TABLE "pixel" (
+ "pixel_id" UUID NOT NULL,
+ "name" VARCHAR(100) NOT NULL,
+ "slug" VARCHAR(100) NOT NULL,
+ "user_id" UUID,
+ "team_id" UUID,
+ "created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
+ "updated_at" TIMESTAMPTZ(6),
+ "deleted_at" TIMESTAMPTZ(6),
+
+ CONSTRAINT "pixel_pkey" PRIMARY KEY ("pixel_id")
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "link_link_id_key" ON "link"("link_id");
+
+-- CreateIndex
+CREATE INDEX "link_slug_idx" ON "link"("slug");
+
+-- CreateIndex
+CREATE INDEX "link_user_id_idx" ON "link"("user_id");
+
+-- CreateIndex
+CREATE INDEX "link_team_id_idx" ON "link"("team_id");
+
+-- CreateIndex
+CREATE INDEX "link_created_at_idx" ON "link"("created_at");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "pixel_pixel_id_key" ON "pixel"("pixel_id");
+
+-- CreateIndex
+CREATE INDEX "pixel_slug_idx" ON "pixel"("slug");
+
+-- CreateIndex
+CREATE INDEX "pixel_user_id_idx" ON "pixel"("user_id");
+
+-- CreateIndex
+CREATE INDEX "pixel_team_id_idx" ON "pixel"("team_id");
+
+-- CreateIndex
+CREATE INDEX "pixel_created_at_idx" ON "pixel"("created_at");
diff --git a/db/postgresql/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml
similarity index 100%
rename from db/postgresql/migrations/migration_lock.toml
rename to prisma/migrations/migration_lock.toml
diff --git a/db/postgresql/schema.prisma b/prisma/schema.prisma
similarity index 83%
rename from db/postgresql/schema.prisma
rename to prisma/schema.prisma
index 3f5008b7..956164db 100644
--- a/db/postgresql/schema.prisma
+++ b/prisma/schema.prisma
@@ -20,10 +20,12 @@ model User {
updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6)
deletedAt DateTime? @map("deleted_at") @db.Timestamptz(6)
- websiteUser Website[] @relation("user")
- websiteCreateUser Website[] @relation("createUser")
- teamUser TeamUser[]
- report Report[]
+ websites Website[] @relation("user")
+ createdBy Website[] @relation("createUser")
+ links Link[] @relation("user")
+ pixels Pixel[] @relation("user")
+ teams TeamUser[]
+ reports Report[]
@@map("user")
}
@@ -42,9 +44,9 @@ model Session {
distinctId String? @map("distinct_id") @db.VarChar(50)
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
- websiteEvent WebsiteEvent[]
- sessionData SessionData[]
- revenue Revenue[]
+ websiteEvents WebsiteEvent[]
+ sessionData SessionData[]
+ revenue Revenue[]
@@index([createdAt])
@@index([websiteId])
@@ -77,10 +79,10 @@ model Website {
createUser User? @relation("createUser", fields: [createdBy], references: [id])
team Team? @relation(fields: [teamId], references: [id])
eventData EventData[]
- report Report[]
+ reports Report[]
revenue Revenue[]
+ segments Segment[]
sessionData SessionData[]
- segment Segment[]
@@index([userId])
@@index([teamId])
@@ -192,8 +194,10 @@ model Team {
updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6)
deletedAt DateTime? @map("deleted_at") @db.Timestamptz(6)
- website Website[]
- teamUser TeamUser[]
+ websites Website[]
+ members TeamUser[]
+ links Link[]
+ pixels Pixel[]
@@index([accessCode])
@@map("team")
@@ -219,7 +223,7 @@ model Report {
id String @id() @unique() @map("report_id") @db.Uuid
userId String @map("user_id") @db.Uuid
websiteId String @map("website_id") @db.Uuid
- type String @db.VarChar(200)
+ type String @db.VarChar(50)
name String @db.VarChar(200)
description String @db.VarChar(500)
parameters Json
@@ -239,7 +243,7 @@ model Report {
model Segment {
id String @id() @unique() @map("segment_id") @db.Uuid
websiteId String @map("website_id") @db.Uuid
- type String @db.VarChar(200)
+ type String @db.VarChar(50)
name String @db.VarChar(200)
parameters Json
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
@@ -257,7 +261,7 @@ model Revenue {
sessionId String @map("session_id") @db.Uuid
eventId String @map("event_id") @db.Uuid
eventName String @map("event_name") @db.VarChar(50)
- currency String @db.VarChar(100)
+ currency String @db.VarChar(10)
revenue Decimal? @db.Decimal(19, 4)
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
@@ -270,3 +274,44 @@ model Revenue {
@@index([websiteId, sessionId, createdAt])
@@map("revenue")
}
+
+model Link {
+ id String @id() @unique() @map("link_id") @db.Uuid
+ name String @db.VarChar(100)
+ url String @db.VarChar(500)
+ slug String @db.VarChar(100)
+ userId String? @map("user_id") @db.Uuid
+ teamId String? @map("team_id") @db.Uuid
+ createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
+ updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6)
+ deletedAt DateTime? @map("deleted_at") @db.Timestamptz(6)
+
+ user User? @relation("user", fields: [userId], references: [id])
+ team Team? @relation(fields: [teamId], references: [id])
+
+ @@index([slug])
+ @@index([userId])
+ @@index([teamId])
+ @@index([createdAt])
+ @@map("link")
+}
+
+model Pixel {
+ id String @id() @unique() @map("pixel_id") @db.Uuid
+ name String @db.VarChar(100)
+ slug String @db.VarChar(100)
+ userId String? @map("user_id") @db.Uuid
+ teamId String? @map("team_id") @db.Uuid
+ createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
+ updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamptz(6)
+ deletedAt DateTime? @map("deleted_at") @db.Timestamptz(6)
+
+ user User? @relation("user", fields: [userId], references: [id])
+ team Team? @relation(fields: [teamId], references: [id])
+
+ @@index([slug])
+ @@index([userId])
+ @@index([teamId])
+ @@index([createdAt])
+ @@map("pixel")
+}
diff --git a/scripts/check-db.js b/scripts/check-db.js
index f93135ab..f6781f1c 100644
--- a/scripts/check-db.js
+++ b/scripts/check-db.js
@@ -7,21 +7,13 @@ import semver from 'semver';
import { PrismaClient } from '@prisma/client';
import { PrismaPg } from '@prisma/adapter-pg';
+const MIN_VERSION = '9.4.0';
+
if (process.env.SKIP_DB_CHECK) {
console.log('Skipping database check.');
process.exit(0);
}
-function getDatabaseType(url = process.env.DATABASE_URL) {
- const type = url && url.split(':')[0];
-
- if (type === 'postgres') {
- return 'postgresql';
- }
-
- return type;
-}
-
const url = new URL(process.env.DATABASE_URL);
const adapter = new PrismaPg(
@@ -61,35 +53,15 @@ async function checkDatabaseVersion() {
const query = await prisma.$queryRaw`select version() as version`;
const version = semver.valid(semver.coerce(query[0].version));
- const databaseType = getDatabaseType();
- const minVersion = databaseType === 'postgresql' ? '9.4.0' : '5.7.0';
-
- if (semver.lt(version, minVersion)) {
+ if (semver.lt(version, MIN_VERSION)) {
throw new Error(
- `Database version is not compatible. Please upgrade ${databaseType} version to ${minVersion} or greater`,
+ `Database version is not compatible. Please upgrade to ${MIN_VERSION} or greater.`,
);
}
success('Database version check successful.');
}
-async function checkV1Tables() {
- try {
- // check for v1 migrations before v2 release date
- const record =
- await prisma.$queryRaw`select * from _prisma_migrations where started_at < '2023-04-17'`;
-
- if (record.length > 0) {
- error(
- 'Umami v1 tables detected. For how to upgrade from v1 to v2 go to https://umami.is/docs/migrate-v1-v2.',
- );
- process.exit(1);
- }
- } catch {
- // Ignore
- }
-}
-
async function applyMigration() {
if (!process.env.SKIP_DB_MIGRATION) {
console.log(execSync('prisma migrate deploy').toString());
@@ -100,13 +72,7 @@ async function applyMigration() {
(async () => {
let err = false;
- for (const fn of [
- checkEnv,
- checkConnection,
- checkDatabaseVersion,
- checkV1Tables,
- applyMigration,
- ]) {
+ for (const fn of [checkEnv, checkConnection, checkDatabaseVersion, applyMigration]) {
try {
await fn();
} catch (e) {
diff --git a/scripts/copy-db-files.js b/scripts/copy-db-files.js
deleted file mode 100644
index c787d179..00000000
--- a/scripts/copy-db-files.js
+++ /dev/null
@@ -1,32 +0,0 @@
-/* eslint-disable no-console */
-import 'dotenv/config';
-import fse from 'fs-extra';
-import path from 'node:path';
-import del from 'del';
-
-function getDatabaseType(url = process.env.DATABASE_URL) {
- const type = process.env.DATABASE_TYPE || (url && url.split(':')[0]);
-
- if (type === 'postgres') {
- return 'postgresql';
- }
-
- return type;
-}
-
-const databaseType = getDatabaseType();
-
-if (!databaseType || !['mysql', 'postgresql'].includes(databaseType)) {
- throw new Error('Missing or invalid database');
-}
-
-console.log(`Database type detected: ${databaseType}`);
-
-const src = path.resolve(process.cwd(), `db/${databaseType}`);
-const dest = path.resolve(process.cwd(), 'prisma');
-
-del.sync(dest);
-
-fse.copySync(src, dest);
-
-console.log(`Copied ${src} to ${dest}`);
diff --git a/src/app/(main)/SideNav.tsx b/src/app/(main)/SideNav.tsx
index 57dd0a8e..895c48cc 100644
--- a/src/app/(main)/SideNav.tsx
+++ b/src/app/(main)/SideNav.tsx
@@ -83,6 +83,9 @@ export function SideNav(props: SidebarProps) {
{!isCollapsed && !hasNav && }
+
+
+
{links.map(({ id, path, label, icon }) => {
return (
@@ -101,9 +104,6 @@ export function SideNav(props: SidebarProps) {
);
})}
-
-
-
);
diff --git a/src/app/(main)/boards/BoardsPage.tsx b/src/app/(main)/boards/BoardsPage.tsx
index 754d898b..991ef965 100644
--- a/src/app/(main)/boards/BoardsPage.tsx
+++ b/src/app/(main)/boards/BoardsPage.tsx
@@ -1,6 +1,5 @@
'use client';
import { Column } from '@umami/react-zen';
-import Link from 'next/link';
import { PageHeader } from '@/components/common/PageHeader';
import { PageBody } from '@/components/common/PageBody';
import { BoardAddButton } from './BoardAddButton';
@@ -12,9 +11,6 @@ export function BoardsPage() {
-
- Board 1
-
);
diff --git a/src/app/(main)/links/LinkAddButton.tsx b/src/app/(main)/links/LinkAddButton.tsx
new file mode 100644
index 00000000..dc819ef3
--- /dev/null
+++ b/src/app/(main)/links/LinkAddButton.tsx
@@ -0,0 +1,31 @@
+import { useMessages, useModified } from '@/components/hooks';
+import { Button, Icon, Modal, Dialog, DialogTrigger, Text, useToast } from '@umami/react-zen';
+import { Plus } from '@/components/icons';
+import { LinkEditForm } from './LinkEditForm';
+
+export function LinkAddButton({ teamId }: { teamId?: string }) {
+ const { formatMessage, labels, messages } = useMessages();
+ const { toast } = useToast();
+ const { touch } = useModified();
+
+ const handleSave = async () => {
+ toast(formatMessage(messages.saved));
+ touch('links');
+ };
+
+ return (
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/(main)/links/LinkDeleteButton.tsx b/src/app/(main)/links/LinkDeleteButton.tsx
new file mode 100644
index 00000000..015af001
--- /dev/null
+++ b/src/app/(main)/links/LinkDeleteButton.tsx
@@ -0,0 +1,55 @@
+import { Dialog } from '@umami/react-zen';
+import { ActionButton } from '@/components/input/ActionButton';
+import { Trash } from '@/components/icons';
+import { ConfirmationForm } from '@/components/common/ConfirmationForm';
+import { messages } from '@/components/messages';
+import { useApi, useMessages, useModified } from '@/components/hooks';
+
+export function LinkDeleteButton({
+ linkId,
+ websiteId,
+ name,
+ onSave,
+}: {
+ linkId: string;
+ websiteId: string;
+ name: string;
+ onSave?: () => void;
+}) {
+ const { formatMessage, labels } = useMessages();
+ const { del, useMutation } = useApi();
+ const { mutate, isPending, error } = useMutation({
+ mutationFn: () => del(`/websites/${websiteId}/links/${linkId}`),
+ });
+ const { touch } = useModified();
+
+ const handleConfirm = (close: () => void) => {
+ mutate(null, {
+ onSuccess: () => {
+ touch('links');
+ onSave?.();
+ close();
+ },
+ });
+ };
+
+ return (
+ }>
+
+
+ );
+}
diff --git a/src/app/(main)/links/LinkEditButton.tsx b/src/app/(main)/links/LinkEditButton.tsx
new file mode 100644
index 00000000..e7c0bfbe
--- /dev/null
+++ b/src/app/(main)/links/LinkEditButton.tsx
@@ -0,0 +1,19 @@
+import { ActionButton } from '@/components/input/ActionButton';
+import { Edit } from '@/components/icons';
+import { Dialog } from '@umami/react-zen';
+import { LinkEditForm } from './LinkEditForm';
+import { useMessages } from '@/components/hooks';
+
+export function LinkEditButton({ linkId }: { linkId: string }) {
+ const { formatMessage, labels } = useMessages();
+
+ return (
+ }>
+
+
+ );
+}
diff --git a/src/app/(main)/links/LinkEditForm.tsx b/src/app/(main)/links/LinkEditForm.tsx
new file mode 100644
index 00000000..f46081ee
--- /dev/null
+++ b/src/app/(main)/links/LinkEditForm.tsx
@@ -0,0 +1,113 @@
+import {
+ Form,
+ FormField,
+ FormSubmitButton,
+ Row,
+ TextField,
+ Button,
+ Text,
+ Label,
+ Column,
+ Icon,
+ Loading,
+} from '@umami/react-zen';
+import { useConfig, useLinkQuery } from '@/components/hooks';
+import { useMessages } from '@/components/hooks';
+import { Refresh } from '@/components/icons';
+import { getRandomChars } from '@/lib/crypto';
+import { useUpdateQuery } from '@/components/hooks/queries/useUpdateQuery';
+
+const generateId = () => getRandomChars(9);
+
+export function LinkEditForm({
+ linkId,
+ teamId,
+ onSave,
+ onClose,
+}: {
+ linkId?: string;
+ teamId?: string;
+ onSave?: () => void;
+ onClose?: () => void;
+}) {
+ const { formatMessage, labels } = useMessages();
+ const { mutate, error, isPending } = useUpdateQuery('/links', { id: linkId, teamId });
+ const { linkDomain } = useConfig();
+ const { data, isLoading } = useLinkQuery(linkId);
+
+ const handleSubmit = async (data: any) => {
+ mutate(data, {
+ onSuccess: async () => {
+ onSave?.();
+ onClose?.();
+ },
+ });
+ };
+
+ if (linkId && !isLoading) {
+ return ;
+ }
+
+ return (
+
+ );
+}
diff --git a/src/app/(main)/links/LinksDataTable.tsx b/src/app/(main)/links/LinksDataTable.tsx
new file mode 100644
index 00000000..043cd768
--- /dev/null
+++ b/src/app/(main)/links/LinksDataTable.tsx
@@ -0,0 +1,14 @@
+import { useLinksQuery, useNavigation } from '@/components/hooks';
+import { LinksTable } from './LinksTable';
+import { DataGrid } from '@/components/common/DataGrid';
+
+export function LinksDataTable() {
+ const { teamId } = useNavigation();
+ const query = useLinksQuery({ teamId });
+
+ return (
+
+ {({ data }) => }
+
+ );
+}
diff --git a/src/app/(main)/links/LinksPage.tsx b/src/app/(main)/links/LinksPage.tsx
index 1b680dd8..766974f5 100644
--- a/src/app/(main)/links/LinksPage.tsx
+++ b/src/app/(main)/links/LinksPage.tsx
@@ -2,22 +2,24 @@
import { PageBody } from '@/components/common/PageBody';
import { Column } from '@umami/react-zen';
import { PageHeader } from '@/components/common/PageHeader';
-import { BoardAddButton } from '@/app/(main)/boards/BoardAddButton';
-import Link from 'next/link';
-import { useMessages } from '@/components/hooks';
+import { LinkAddButton } from './LinkAddButton';
+import { useMessages, useNavigation } from '@/components/hooks';
+import { LinksDataTable } from '@/app/(main)/links/LinksDataTable';
+import { Panel } from '@/components/common/Panel';
export function LinksPage() {
const { formatMessage, labels } = useMessages();
+ const { teamId } = useNavigation();
return (
-
+
-
+
-
- Board 1
-
+
+
+
);
diff --git a/src/app/(main)/links/LinksTable.tsx b/src/app/(main)/links/LinksTable.tsx
new file mode 100644
index 00000000..e06e2537
--- /dev/null
+++ b/src/app/(main)/links/LinksTable.tsx
@@ -0,0 +1,37 @@
+import { DataTable, DataColumn, Row } from '@umami/react-zen';
+import { useMessages, useNavigation } from '@/components/hooks';
+import { Empty } from '@/components/common/Empty';
+import { DateDistance } from '@/components/common/DateDistance';
+import { LinkEditButton } from './LinkEditButton';
+import { LinkDeleteButton } from './LinkDeleteButton';
+
+export function LinksTable({ data = [] }) {
+ const { formatMessage, labels } = useMessages();
+ const { websiteId } = useNavigation();
+
+ if (data.length === 0) {
+ return ;
+ }
+
+ return (
+
+
+
+
+ {(row: any) => }
+
+
+ {(row: any) => {
+ const { id, name } = row;
+
+ return (
+
+
+
+
+ );
+ }}
+
+
+ );
+}
diff --git a/src/app/(main)/pixels/PixelAddButton.tsx b/src/app/(main)/pixels/PixelAddButton.tsx
new file mode 100644
index 00000000..a74a455e
--- /dev/null
+++ b/src/app/(main)/pixels/PixelAddButton.tsx
@@ -0,0 +1,32 @@
+import { useMessages, useModified, useNavigation } from '@/components/hooks';
+import { Button, Icon, Modal, Dialog, DialogTrigger, Text, useToast } from '@umami/react-zen';
+import { Plus } from '@/components/icons';
+import { PixelAddForm } from './PixelAddForm';
+
+export function PixelAddButton() {
+ const { formatMessage, labels, messages } = useMessages();
+ const { toast } = useToast();
+ const { touch } = useModified();
+ const { teamId } = useNavigation();
+
+ const handleSave = async () => {
+ toast(formatMessage(messages.saved));
+ touch('boards');
+ };
+
+ return (
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/(main)/pixels/PixelAddForm.tsx b/src/app/(main)/pixels/PixelAddForm.tsx
new file mode 100644
index 00000000..56ce44b1
--- /dev/null
+++ b/src/app/(main)/pixels/PixelAddForm.tsx
@@ -0,0 +1,62 @@
+import { Form, FormField, FormSubmitButton, Row, TextField, Button } from '@umami/react-zen';
+import { useApi } from '@/components/hooks';
+import { DOMAIN_REGEX } from '@/lib/constants';
+import { useMessages } from '@/components/hooks';
+
+export function PixelAddForm({
+ teamId,
+ onSave,
+ onClose,
+}: {
+ teamId?: string;
+ onSave?: () => void;
+ onClose?: () => void;
+}) {
+ const { formatMessage, labels, messages } = useMessages();
+ const { post, useMutation } = useApi();
+ const { mutate, error, isPending } = useMutation({
+ mutationFn: (data: any) => post('/pixels', { ...data, teamId }),
+ });
+
+ const handleSubmit = async (data: any) => {
+ mutate(data, {
+ onSuccess: async () => {
+ onSave?.();
+ onClose?.();
+ },
+ });
+ };
+
+ return (
+
+ );
+}
diff --git a/src/app/(main)/pixels/PixelsPage.tsx b/src/app/(main)/pixels/PixelsPage.tsx
index fa686620..a7a9e13a 100644
--- a/src/app/(main)/pixels/PixelsPage.tsx
+++ b/src/app/(main)/pixels/PixelsPage.tsx
@@ -2,8 +2,7 @@
import { PageBody } from '@/components/common/PageBody';
import { Column } from '@umami/react-zen';
import { PageHeader } from '@/components/common/PageHeader';
-import { BoardAddButton } from '@/app/(main)/boards/BoardAddButton';
-import Link from 'next/link';
+import { PixelAddButton } from './PixelAddButton';
import { useMessages } from '@/components/hooks';
export function PixelsPage() {
@@ -13,11 +12,8 @@ export function PixelsPage() {
-
+
-
- Board 1
-
);
diff --git a/src/app/(main)/settings/teams/TeamsTable.tsx b/src/app/(main)/settings/teams/TeamsTable.tsx
index a2599251..7d6e890a 100644
--- a/src/app/(main)/settings/teams/TeamsTable.tsx
+++ b/src/app/(main)/settings/teams/TeamsTable.tsx
@@ -21,13 +21,13 @@ export function TeamsTable({
{(row: any) => {row.name}}
- {(row: any) => row.teamUser.find(({ role }) => role === ROLES.teamOwner)?.user?.username}
+ {(row: any) => row.users.find(({ role }) => role === ROLES.teamOwner)?.user?.username}
-
- {(row: any) => row._count.website}
+
+ {(row: any) => row._count.websites}
-
- {(row: any) => row._count.teamUser}
+
+ {(row: any) => row._count.users}
{showActions ? (
diff --git a/src/app/(main)/settings/teams/[teamId]/TeamMembersPage.tsx b/src/app/(main)/settings/teams/[teamId]/TeamMembersPage.tsx
index 75396bcf..bf245c49 100644
--- a/src/app/(main)/settings/teams/[teamId]/TeamMembersPage.tsx
+++ b/src/app/(main)/settings/teams/[teamId]/TeamMembersPage.tsx
@@ -1,19 +1,17 @@
'use client';
-import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
import { TeamMembersDataTable } from './TeamMembersDataTable';
import { SectionHeader } from '@/components/common/SectionHeader';
-import { useLoginQuery, useMessages } from '@/components/hooks';
+import { useLoginQuery, useMessages, useTeam } from '@/components/hooks';
import { ROLES } from '@/lib/constants';
-import { useContext } from 'react';
import { Column } from '@umami/react-zen';
export function TeamMembersPage({ teamId }: { teamId: string }) {
- const team = useContext(TeamContext);
+ const team = useTeam();
const { user } = useLoginQuery();
const { formatMessage, labels } = useMessages();
const canEdit =
- team?.teamUser?.find(
+ team?.members?.find(
({ userId, role }) =>
(role === ROLES.teamOwner || role === ROLES.teamManager) && userId === user.id,
) && user.role !== ROLES.viewOnly;
diff --git a/src/app/(main)/settings/websites/WebsitesTable.tsx b/src/app/(main)/settings/websites/WebsitesTable.tsx
index 2d285c75..c2077eb1 100644
--- a/src/app/(main)/settings/websites/WebsitesTable.tsx
+++ b/src/app/(main)/settings/websites/WebsitesTable.tsx
@@ -57,7 +57,7 @@ export function WebsitesTable({
)}
{allowEdit && (
-