mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
fix: feature-base-refactoring (#297)
* docs: add AGENTS.md with feature-based architecture conventions Defines the target src/ structure, feature list, naming conventions (kebab-case, file type patterns), import direction rules, and feature boundary guidelines for the planned big-bang refactor. * docs: add features/channel/ to architecture conventions Generic channel UI (card, modal, form) goes to features/channel/. Provider-specific sub-folders: channel/notifications/ and channel/storages/. * refactor: move event emitter to lib/, project service to db/services/, dedup use-mobile hook * fix: remove unused auth import in db/services/project.ts * refactor: migrate wrappers/common to components/common (flat) * fix: remove dead commented-out code from button-with-loading.tsx * refactor: merge features/browser into features/theme, flatten upload/updates, migrate statistics * refactor: migrate wrappers/auth to features/auth Move all auth components and forms from src/components/wrappers/auth/ into src/features/auth/ with flat kebab-case naming. Handle extra reset-password-form.tsx and differing reset-password-schema.ts by copying them as reset-password-page-form.tsx and reset-password-page.schema.ts. * refactor: remove dead auth files and fix zPassword schema Delete reset-password-section.tsx and reset-password-page-form.tsx (zero consumers). Fix reset-password-page.schema.ts to import zPassword from @/lib/zod. * refactor: migrate wrappers/dashboard/common to features/layout, rename Header.tsx to header.tsx * fix: decouple layout from theme/updates cross-feature imports Use slot props (actions, updateNotification) so app/ composes ModeToggle and UpdateNotification into Header and AppSidebar. Delete dead side-bar-logo.tsx (zero consumers). * refactor: merge features/keys into features/agents, flatten sub-dirs, migrate agent wrappers - Merge src/features/keys/keys.action.ts into src/features/agents/ - Flatten src/features/agents/components/ and hooks/ into feature root - Migrate all src/components/wrappers/dashboard/agent/ files into src/features/agents/ - Update all import paths across app/ and src/ accordingly - Fix broken code-snippet import in agent-key-modal.tsx (was pointing to non-existent wrappers path) * fix: move github-releases to lib/, remove dead code and unused imports Move GitHub release fetch logic from features/updates/github.ts to lib/github-releases.ts so both features/updates and features/agents can import it without cross-feature imports. Remove commented-out impersonation block from agent-database-card.tsx. Remove unused imports (CopyButton, Terminal, Key, Info). * refactor: flatten features/projects/components, migrate project wrappers Move project.dialog/form out of sub-dir, migrate project-card, project-database-card, and delete-project button/action from wrappers into features/projects/. Update all import paths across src/ and app/. Leave wrappers/dashboard/projects/database/ untouched for Task 10. * fix: extract DatabaseCard to components/common, remove cross-feature import DatabaseCard was imported by both features/projects and features/agents, violating the no-cross-feature-import rule. Extract to components/common/ database-card.tsx so both features import from the shared layer. Also tighten extendedProps type and remove commented-out maxCount prop. * refactor: migrate all database/backup/restore/health UI to features/database Consolidates ~40 files from 5 source locations (features/dashboard/backup, features/dashboard/restore, wrappers/dashboard/backup, wrappers/dashboard/database, wrappers/dashboard/health, wrappers/dashboard/projects/database) into the new flat features/database directory with kebab-case filenames and updated @/ imports. * fix: remove dead restore-form files and console.log from health-grid Delete restore-form.tsx (186 lines all commented-out, zero consumers) and restore-form.schema.ts (only a commented import). Remove console.log(logs) from getOldestLog helper in health-grid.tsx. Cross-feature imports (database→storages for dispatchStorage/storeBackupFiles) are accepted as known architectural coupling — storage dispatch is called from database server actions at runtime and cannot route through app/ props. * refactor: migrate wrappers/admin/users to features/users, fix table-colums typo * fix: fix relative imports, string interpolation, and console.error in users feature Replace relative imports with @/ aliases in user-actions-cell.tsx and admin-user-edit-modal.tsx. Fix string interpolation bug in dialog title (was regular string, now template literal). Remove console.error from admin-user-change-role-modal.tsx. * refactor: migrate wrappers/profile to features/profile, fix profile-apperance typo * fix: remove console.error calls and ts-ignore in profile feature Remove console.error from profile-account.tsx, 2fa-form.tsx (x2). Replace @ts-ignore on fileInput.onchange with typed cast in avatar-with-upload.tsx. * refactor: rename features/organization to features/organizations, migrate all org wrappers Moves 37 org-related files from features/organization/, features/dashboard/, hooks/, wrappers/admin/organizations/, wrappers/admin/channels/organization/, and wrappers/dashboard/organization/ into a flat features/organizations/ feature directory. Updates all import paths codebase-wide. Fixes @ts-ignore and removes unused @better-fetch/fetch import in admin-organization-form. * fix: remove console.error, @ts-ignore, and unused destructures in features/organizations * refactor: migrate wrappers/organization/migration to features/migration Move all 5 migration files to src/features/migration/, update internal imports to @/features/migration/ absolute paths, remove console.log, and delete src/components/wrappers/dashboard/organization/ entirely. * refactor: migrate wrappers/admin/settings to features/settings Move all 14 settings files (email, notification, storage, s3) from wrappers/dashboard/admin/settings/ into features/settings/, rewriting imports, replacing @ts-ignore with @ts-expect-error, and renaming error params to _error where applicable. * refactor: rename notifications files to kebab convention, migrate notification logs - Rename dispatch.ts/helpers.ts/types.ts to notifications.*.ts - Migrate notification logs components from wrappers/dashboard/admin/notifications/logs/ to features/notifications/ - Fix all import paths across codebase and in providers/ - Remove unused useRouter import, replace @ts-ignore with @ts-expect-error - Delete src/components/wrappers/dashboard/admin/notifications/ * refactor: rename storages dispatch/helpers/types to kebab convention - Rename dispatch.ts/helpers.ts/types.ts to storages.*.ts - Fix all import paths across codebase and in providers/ - Fix relative ../types and ../../types imports in all storage providers to absolute @/ paths - Replace @ts-ignore with @ts-expect-error, remove console.debug/console.error * refactor: create features/channel with generic UI and notifications/storages provider sub-folders Consolidates 4 source locations into src/features/channel/: - components/wrappers/dashboard/admin/channels/ → features/channel/ (11 generic UI files) - features/notifications/providers/ → features/channel/notifications/ (9 backend + 18 form files) - features/storages/providers/ → features/channel/storages/ (backend + forms) The two conflicting google-drive/helpers.ts files (client helpers vs OAuth action) are kept separate: helpers.ts and google-drive-refresh.action.ts. Rewrites all imports codebase-wide to point to new locations. Removes console.log/console.error calls, upgrades @ts-ignore to @ts-expect-error. * fix: remove console.error from local storage provider * fix: multi-select.tsx scroll issue * fix: multi-select.tsx scroll issue
This commit is contained in:
+1
-59
@@ -1,62 +1,4 @@
|
||||
// "use client";
|
||||
//
|
||||
// import { Button } from "@/components/ui/button";
|
||||
// import { ButtonHTMLAttributes } from "react";
|
||||
// import { Loader2 } from "lucide-react";
|
||||
//
|
||||
// export type VariantButton = {
|
||||
// secondary: string;
|
||||
// default: string;
|
||||
// outline: string;
|
||||
// ghost: string;
|
||||
// link: string;
|
||||
// destructive: string;
|
||||
// };
|
||||
// export type sizeButton = {
|
||||
// default: string;
|
||||
// icon: string;
|
||||
// sm: string;
|
||||
// lg: string;
|
||||
// };
|
||||
//
|
||||
// export type ButtonWithConfirmProps = {
|
||||
// icon?: any;
|
||||
// text: string;
|
||||
// variant?: keyof VariantButton;
|
||||
// className?: string;
|
||||
// onClick: () => void;
|
||||
// isPending?: boolean;
|
||||
// size: keyof sizeButton;
|
||||
// };
|
||||
//
|
||||
// export const ButtonWithLoading = ({
|
||||
// icon,
|
||||
// text,
|
||||
// variant,
|
||||
// className,
|
||||
// onClick,
|
||||
// isPending,
|
||||
// size,
|
||||
// ...props // catch all remaining props
|
||||
// }: ButtonWithConfirmProps & ButtonHTMLAttributes<HTMLButtonElement>) => {
|
||||
// return (
|
||||
// <Button
|
||||
// onClick={() => {
|
||||
// onClick();
|
||||
// }}
|
||||
// variant={variant ? variant : "default"}
|
||||
// className={className}
|
||||
// {...props} // forward the remaining props to the Button component
|
||||
// size={size || "default"}
|
||||
// >
|
||||
// {isPending && <Loader2 className="animate-spin mr-4" size={16} />}
|
||||
// {text}
|
||||
// <>{icon ? icon : null}</>
|
||||
// </Button>
|
||||
// );
|
||||
// };
|
||||
|
||||
'use client'
|
||||
"use client"
|
||||
|
||||
import { ButtonHTMLAttributes, ReactNode } from "react";
|
||||
import { Loader2 } from "lucide-react";
|
||||
+2
-2
@@ -3,8 +3,8 @@
|
||||
import React, { ComponentType, useState } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { PaginationNavigation } from "@/components/wrappers/common/pagination/pagination-navigation";
|
||||
import { PaginationSize } from "@/components/wrappers/common/pagination/pagination-size";
|
||||
import { PaginationNavigation } from "@/components/common/pagination-navigation";
|
||||
import { PaginationSize } from "@/components/common/pagination-size";
|
||||
|
||||
interface CardsWithPaginationProps<T> {
|
||||
className?: string;
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
import {PropsWithChildren, useState} from "react";
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {Check, Copy, Terminal} from "lucide-react";
|
||||
import {copyToClipboardWithMeta} from "@/components/wrappers/common/button/copy-button";
|
||||
import {copyToClipboardWithMeta} from "@/components/common/copy-button";
|
||||
import {cn} from "@/lib/utils";
|
||||
|
||||
export type CodeSnippetProps = PropsWithChildren<{
|
||||
+3
-21
@@ -4,35 +4,17 @@ import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import {useState} from "react";
|
||||
import {Card} from "@/components/ui/card";
|
||||
import {ConnectionIndicator} from "@/components/wrappers/common/connection-indicator";
|
||||
import {ConnectionIndicator} from "@/components/common/connection-indicator";
|
||||
import {formatDateLastContact} from "@/utils/date-formatting";
|
||||
import {Database} from "@/db/schema/07_database";
|
||||
import {ChevronRight, Activity, Fingerprint, Copy, Check} from "lucide-react";
|
||||
|
||||
export type projectDatabaseCardProps = {
|
||||
data: Database;
|
||||
extendedProps: any;
|
||||
organizationSlug: string;
|
||||
};
|
||||
|
||||
export const ProjectDatabaseCard = (props: projectDatabaseCardProps) => {
|
||||
const {data: database, extendedProps: extendedProps} = props;
|
||||
|
||||
return (
|
||||
<Link
|
||||
className="group block transition-all duration-200 outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-xl"
|
||||
href={`/dashboard/projects/${extendedProps.id}/database/${database.id}`}>
|
||||
<DatabaseCard data={database}/>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export type databaseCardProps = {
|
||||
export type DatabaseCardProps = {
|
||||
data: Database;
|
||||
withDetails?: boolean;
|
||||
};
|
||||
|
||||
export const DatabaseCard = (props: databaseCardProps) => {
|
||||
export const DatabaseCard = (props: DatabaseCardProps) => {
|
||||
const {data: database, withDetails = true} = props;
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
|
||||
+67
-35
@@ -1,5 +1,3 @@
|
||||
//https://github.com/sersavan/shadcn-multi-select-component
|
||||
|
||||
import * as React from "react";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { CheckIcon, XCircle, ChevronDown, XIcon, WandSparkles } from "lucide-react";
|
||||
@@ -233,53 +231,87 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start" onEscapeKeyDown={() => setIsPopoverOpen(false)}>
|
||||
<PopoverContent
|
||||
className="w-auto p-0"
|
||||
align="start"
|
||||
onEscapeKeyDown={() => setIsPopoverOpen(false)}
|
||||
>
|
||||
<Command>
|
||||
<CommandInput placeholder="Search..." onKeyDown={handleInputKeyDown} />
|
||||
<CommandList>
|
||||
<CommandList className="max-h-none overflow-visible">
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
<CommandItem key="all" onSelect={toggleAll} className="cursor-pointer">
|
||||
<div
|
||||
className={cn(
|
||||
"mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
|
||||
selectedValues.length === options.length ? "bg-primary text-primary-foreground" : "opacity-50 [&_svg]:invisible"
|
||||
)}
|
||||
>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
</div>
|
||||
<span>(Select All)</span>
|
||||
</CommandItem>
|
||||
{options.map((option) => {
|
||||
const isSelected = selectedValues.includes(option.value);
|
||||
return (
|
||||
<CommandItem key={option.value} onSelect={() => toggleOption(option.value)} className="cursor-pointer">
|
||||
<div
|
||||
className={cn(
|
||||
"mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
|
||||
isSelected ? "bg-primary text-primary-foreground" : "opacity-50 [&_svg]:invisible"
|
||||
)}
|
||||
<div
|
||||
className="max-h-[18rem] overflow-y-auto overflow-x-hidden overscroll-contain touch-pan-y pr-1"
|
||||
style={{ WebkitOverflowScrolling: "touch" }}
|
||||
onWheelCapture={(event) => event.stopPropagation()}
|
||||
onTouchMoveCapture={(event) => event.stopPropagation()}
|
||||
>
|
||||
<CommandGroup>
|
||||
<CommandItem key="all" onSelect={toggleAll} className="cursor-pointer">
|
||||
<div
|
||||
className={cn(
|
||||
"mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
|
||||
selectedValues.length === options.length
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "opacity-50 [&_svg]:invisible"
|
||||
)}
|
||||
>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
</div>
|
||||
<span>(Select All)</span>
|
||||
</CommandItem>
|
||||
|
||||
{options.map((option) => {
|
||||
const isSelected = selectedValues.includes(option.value);
|
||||
|
||||
return (
|
||||
<CommandItem
|
||||
key={option.value}
|
||||
onSelect={() => toggleOption(option.value)}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
</div>
|
||||
{option.icon && <option.icon className="mr-2 h-4 w-4 text-muted-foreground" />}
|
||||
<span>{option.label}</span>
|
||||
</CommandItem>
|
||||
);
|
||||
})}
|
||||
</CommandGroup>
|
||||
<div
|
||||
className={cn(
|
||||
"mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
|
||||
isSelected
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "opacity-50 [&_svg]:invisible"
|
||||
)}
|
||||
>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
</div>
|
||||
|
||||
{option.icon && (
|
||||
<option.icon className="mr-2 h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
|
||||
<span>{option.label}</span>
|
||||
</CommandItem>
|
||||
);
|
||||
})}
|
||||
</CommandGroup>
|
||||
</div>
|
||||
|
||||
<CommandSeparator />
|
||||
|
||||
<CommandGroup>
|
||||
<div className="flex items-center justify-between">
|
||||
{selectedValues.length > 0 && (
|
||||
<>
|
||||
<CommandItem onSelect={handleClear} className="flex-1 justify-center cursor-pointer">
|
||||
<CommandItem
|
||||
onSelect={handleClear}
|
||||
className="flex-1 justify-center cursor-pointer"
|
||||
>
|
||||
Clear
|
||||
</CommandItem>
|
||||
<Separator orientation="vertical" className="flex min-h-6 h-full" />
|
||||
</>
|
||||
)}
|
||||
<CommandItem onSelect={() => setIsPopoverOpen(false)} className="flex-1 justify-center cursor-pointer max-w-full">
|
||||
|
||||
<CommandItem
|
||||
onSelect={() => setIsPopoverOpen(false)}
|
||||
className="flex-1 justify-center cursor-pointer max-w-full"
|
||||
>
|
||||
Close
|
||||
</CommandItem>
|
||||
</div>
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { Pagination, PaginationContent, PaginationItem, PaginationNext, PaginationPrevious } from "@/components/ui/pagination";
|
||||
import { PaginationIndexes } from "@/components/wrappers/common/pagination/pagination-indexes";
|
||||
import { PaginationIndexes } from "@/components/common/pagination-indexes";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export type paginationNavigationProps = {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { PaginationNavigation } from "@/components/wrappers/common/pagination/pagination-navigation";
|
||||
import { PaginationNavigation } from "@/components/common/pagination-navigation";
|
||||
|
||||
export type paginationNavigationProps = {
|
||||
className?: string;
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import {TablePaginationNavigation} from "@/components/wrappers/common/table/table-pagination-navigation";
|
||||
import {TablePaginationSize} from "@/components/wrappers/common/table/table-pagination-size";
|
||||
import {TablePaginationNavigation} from "@/components/common/table-pagination-navigation";
|
||||
import {TablePaginationSize} from "@/components/common/table-pagination-size";
|
||||
import {cn} from "@/lib/utils";
|
||||
|
||||
interface tablePaginationProps {
|
||||
@@ -1,7 +1,7 @@
|
||||
// import * as React from "react";
|
||||
// import EmailLayout from "./email-layout";
|
||||
// import {Text, Section, Button} from "@react-email/components";
|
||||
// import type {EventPayload} from "@/features/notifications/types";
|
||||
// import type {EventPayload} from "@/features/notifications/notifications.types";
|
||||
//
|
||||
// export interface EmailNotificationProps {
|
||||
// payload: EventPayload
|
||||
@@ -48,7 +48,7 @@
|
||||
import * as React from "react";
|
||||
import EmailLayout from "./email-layout";
|
||||
import {Text, Section, Button} from "@react-email/components";
|
||||
import type {EventPayload} from "@/features/notifications/types";
|
||||
import type {EventPayload} from "@/features/notifications/notifications.types";
|
||||
|
||||
export interface EmailNotificationProps {
|
||||
payload: EventPayload;
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import {useRouter} from "next/navigation";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {Form, FormControl, FormField, FormItem, FormLabel, useZodForm} from "@/components/ui/form";
|
||||
import {PasswordStrengthInput} from "@/components/ui/password-input-indicator";
|
||||
import {ResetPasswordSchema, ResetPasswordType} from "@/components/wrappers/auth/reset-password/reset-password-schema";
|
||||
import {PasswordInput} from "@/components/ui/password-input";
|
||||
import {CardContent, CardHeader} from "@/components/ui/card";
|
||||
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading";
|
||||
import {authClient} from "@/lib/auth/auth-client";
|
||||
import {toast} from "sonner";
|
||||
import {CardAuth} from "@/features/layout/card-auth";
|
||||
|
||||
type ResetPasswordFormProps = {
|
||||
token: string;
|
||||
};
|
||||
|
||||
export const ResetPasswordForm = ({token}: ResetPasswordFormProps) => {
|
||||
|
||||
const router = useRouter();
|
||||
const form = useZodForm({
|
||||
schema: ResetPasswordSchema,
|
||||
});
|
||||
|
||||
const mutationResetPassword = useMutation({
|
||||
mutationFn: async (data: ResetPasswordType) => {
|
||||
|
||||
await authClient.resetPassword({
|
||||
newPassword: data.password,
|
||||
token,
|
||||
}, {
|
||||
onSuccess: (response) => {
|
||||
toast.success("Password changed successfully.");
|
||||
setTimeout(() => router.push("/"), 1000);
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(error);
|
||||
toast.error(error.error.message);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<CardAuth>
|
||||
<CardHeader>
|
||||
<div className="grid gap-2 text-center mb-2">
|
||||
<h1 className="text-3xl font-bold">Reset Password</h1>
|
||||
<p className="text-balance text-muted-foreground">Fill information below to change your
|
||||
password</p>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form
|
||||
form={form}
|
||||
className="flex flex-col gap-4"
|
||||
onSubmit={async (values) => {
|
||||
await mutationResetPassword.mutateAsync(values);
|
||||
}}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
defaultValue=""
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<PasswordStrengthInput label={"Enter new password"} field={field}/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="confirmPassword"
|
||||
defaultValue=""
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Confirmation Password</FormLabel>
|
||||
<FormControl>
|
||||
<PasswordInput placeholder={"Confirm password"} {...field}
|
||||
value={field.value ?? ""}/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<ButtonWithLoading type="submit" isPending={mutationResetPassword.isPending}>
|
||||
Validate
|
||||
</ButtonWithLoading>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</CardAuth>
|
||||
);
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import {useRouter, useSearchParams} from "next/navigation";
|
||||
import {ResetPasswordForm} from "@/components/wrappers/auth/reset-password/reset-password-form";
|
||||
import {toast} from "sonner";
|
||||
import {useEffect} from "react";
|
||||
import {LoadingSpinner} from "@/components/wrappers/common/loading/loading-spinner";
|
||||
|
||||
export const ResetPasswordSection = () => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const token = searchParams.get("token");
|
||||
const error = searchParams.get("error");
|
||||
|
||||
useEffect(() => {
|
||||
if (error || !token) {
|
||||
if (error == "INVALID_TOKEN") {
|
||||
toast.error("Invalid reset token");
|
||||
} else {
|
||||
toast.error("An error occurred.");
|
||||
}
|
||||
setTimeout(() => router.push("/"), 1000);
|
||||
}
|
||||
}, [error, token, router]);
|
||||
|
||||
|
||||
if (!token) return (
|
||||
<div className="w-full h-full flex flex-col items-center justify-center">
|
||||
<LoadingSpinner size={50}/>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<ResetPasswordForm token={token}/>
|
||||
)
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import {Database} from "@/db/schema/07_database";
|
||||
import {DatabaseCard} from "@/components/wrappers/dashboard/projects/project-card/project-database-card";
|
||||
|
||||
export type agentDatabaseCardProps = {
|
||||
data: Database;
|
||||
};
|
||||
|
||||
export const AgentDatabaseCard = (props: agentDatabaseCardProps) => {
|
||||
const {data: database} = props;
|
||||
|
||||
return <DatabaseCard withDetails={false} data={database}/>;
|
||||
|
||||
//Todo: Add again when impersonation system will be implemented
|
||||
/* if (!database.projectId) {
|
||||
return <DatabaseCard data={database}/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Link className="group block transition-all duration-200 outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-xl"
|
||||
href={`/dashboard/projects/${database.projectId}/database/${database.id}`}>
|
||||
<DatabaseCard data={database}/>
|
||||
</Link>
|
||||
);*/
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
"use client";
|
||||
import {useSidebar} from "@/components/ui/sidebar";
|
||||
import {useTheme} from "next-themes";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
export type SideBarLogoProps = {};
|
||||
|
||||
export const SideBarLogo = (props: SideBarLogoProps) => {
|
||||
const {state, isMobile} = useSidebar();
|
||||
const {resolvedTheme} = useTheme();
|
||||
return state === "collapsed" && !isMobile ? (
|
||||
<Link href={"/dashboard"}>
|
||||
|
||||
<div className="flex items-center justify-center">
|
||||
<Image
|
||||
src={"/images/logo/picto.png"}
|
||||
alt="Logo"
|
||||
width={32}
|
||||
height={32}
|
||||
loading="eager"
|
||||
className="object-contain"
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
) : (
|
||||
<Link href={"/dashboard"}>
|
||||
|
||||
<div className="w-full h-auto p-4 flex items-center justify-center">
|
||||
<Image
|
||||
loading="eager"
|
||||
src={"/images/logo/logo.png"}
|
||||
alt="Logo"
|
||||
width={190}
|
||||
height={190}
|
||||
className="object-contain"
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
@@ -1,39 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const ImmediateExecutionSchema = z.object({
|
||||
executionMode: z.literal("immediate"),
|
||||
});
|
||||
|
||||
const ScheduledExecutionSchema = z.object({
|
||||
executionMode: z.literal("scheduled"),
|
||||
scheduledDatetime: z.date(),
|
||||
});
|
||||
|
||||
const RemoteImmediateSchema = z
|
||||
.object({
|
||||
backupLocation: z.literal("remote-file"),
|
||||
})
|
||||
.merge(ImmediateExecutionSchema);
|
||||
|
||||
const RemoteScheduledSchema = z
|
||||
.object({
|
||||
backupLocation: z.literal("remote-file"),
|
||||
})
|
||||
.merge(ScheduledExecutionSchema);
|
||||
|
||||
const DesktopImmediateSchema = z
|
||||
.object({
|
||||
backupLocation: z.literal("desktop-file"),
|
||||
uploadedBackupFile: z.instanceof(File),
|
||||
})
|
||||
.merge(ImmediateExecutionSchema);
|
||||
|
||||
const DesktopScheduledSchema = z
|
||||
.object({
|
||||
backupLocation: z.literal("desktop-file"),
|
||||
uploadedBackupFile: z.instanceof(File),
|
||||
})
|
||||
.merge(ScheduledExecutionSchema);
|
||||
|
||||
export const RestoreSchema = z.union([RemoteImmediateSchema, RemoteScheduledSchema, DesktopImmediateSchema, DesktopScheduledSchema]);
|
||||
export type RestoreType = z.infer<typeof RestoreSchema>;
|
||||
@@ -1,186 +0,0 @@
|
||||
// "use client";
|
||||
//
|
||||
// import { useState } from "react";
|
||||
//
|
||||
// import { DateTimePicker } from "@/components/wrappers/common/day-time-picker";
|
||||
// import { Button } from "@/components/ui/button";
|
||||
// import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm } from "@/components/ui/form";
|
||||
// import { Input } from "@/components/ui/input";
|
||||
// import { RestoreSchema } from "@/components/wrappers/dashboard/database/restore-form.schema";
|
||||
// import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
// import { ComboBox } from "@/components/wrappers/common/combobox";
|
||||
// import { Label } from "@/components/ui/label";
|
||||
//
|
||||
// export type restoreFormProps = {
|
||||
// databaseToRestore: any;
|
||||
// databases: any[];
|
||||
// backups: any[];
|
||||
// };
|
||||
//
|
||||
// export const RestoreForm = (props: restoreFormProps) => {
|
||||
// const { databaseToRestore, databases, backups } = props;
|
||||
//
|
||||
// const backupLocations = [
|
||||
// {
|
||||
// value: "remote-file",
|
||||
// label: "Remote File",
|
||||
// },
|
||||
// {
|
||||
// value: "desktop-file",
|
||||
// label: "Desktop File",
|
||||
// },
|
||||
// ];
|
||||
//
|
||||
// const executionModes = [
|
||||
// {
|
||||
// value: "immediate",
|
||||
// label: "Immediate",
|
||||
// },
|
||||
// {
|
||||
// value: "scheduled",
|
||||
// label: "Scheduled",
|
||||
// },
|
||||
// ];
|
||||
//
|
||||
// const [selectedDatabaseId, setSelectedDatabaseId] = useState(databaseToRestore.id);
|
||||
//
|
||||
// const filteredBackups = backups.filter((backup) => backup.databaseId == selectedDatabaseId);
|
||||
//
|
||||
// const defaultValues = {
|
||||
// executionMode: "immediate" as const,
|
||||
// backupLocation: "remote-file" as const,
|
||||
// };
|
||||
//
|
||||
// const form = useZodForm({
|
||||
// schema: RestoreSchema,
|
||||
// defaultValues: defaultValues,
|
||||
// });
|
||||
//
|
||||
// const values = form.getValues();
|
||||
//
|
||||
// return (
|
||||
// <div>
|
||||
// <Form
|
||||
// form={form}
|
||||
// onSubmit={async (values) => {
|
||||
// console.log(values);
|
||||
// }}
|
||||
// >
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="backupLocation"
|
||||
// render={({ field }) => (
|
||||
// <FormItem>
|
||||
// <FormLabel>Backup location</FormLabel>
|
||||
// <Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
// <FormControl>
|
||||
// <SelectTrigger>
|
||||
// <SelectValue />
|
||||
// </SelectTrigger>
|
||||
// </FormControl>
|
||||
// <SelectContent>
|
||||
// {backupLocations.map((backupLocation, key) => (
|
||||
// <SelectItem key={key} value={backupLocation.value}>
|
||||
// {backupLocation.label}
|
||||
// </SelectItem>
|
||||
// ))}
|
||||
// </SelectContent>
|
||||
// </Select>
|
||||
// <FormMessage />
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
//
|
||||
// {values.backupLocation == "remote-file" ? (
|
||||
// <>
|
||||
// <div className="flex flex-col">
|
||||
// <Label>Database</Label>
|
||||
// <ComboBox
|
||||
// values={databases.map((database) => ({ value: database.id, label: database.name }))}
|
||||
// onValueChange={setSelectedDatabaseId}
|
||||
// defaultValue={selectedDatabaseId}
|
||||
// searchField
|
||||
// />
|
||||
// </div>
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="remoteBackup"
|
||||
// render={({ field }) => (
|
||||
// <FormItem className="flex flex-col">
|
||||
// <FormLabel>Remote backup</FormLabel>
|
||||
// <ComboBoxFormItem
|
||||
// values={filteredBackups.map((backup) => ({
|
||||
// value: backup.id,
|
||||
// label: backup.createdAt.toString(),
|
||||
// }))}
|
||||
// {...field}
|
||||
// />
|
||||
// <FormMessage />
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
// </>
|
||||
// ) : null}
|
||||
//
|
||||
// {values.backupLocation == "desktop-file" ? (
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="uploadedBackupFile"
|
||||
// render={({ field }) => (
|
||||
// <FormItem>
|
||||
// <FormLabel>File</FormLabel>
|
||||
// <FormControl>
|
||||
// <Input id="uploadedBackupFile" type="file" {...field} />
|
||||
// </FormControl>
|
||||
// <FormMessage />
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
// ) : null}
|
||||
//
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="executionMode"
|
||||
// render={({ field }) => (
|
||||
// <FormItem>
|
||||
// <FormLabel>Execution mode</FormLabel>
|
||||
// <Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
// <FormControl>
|
||||
// <SelectTrigger>
|
||||
// <SelectValue />
|
||||
// </SelectTrigger>
|
||||
// </FormControl>
|
||||
// <SelectContent>
|
||||
// {executionModes.map((executionMode, key) => (
|
||||
// <SelectItem key={key} value={executionMode.value}>
|
||||
// {executionMode.label}
|
||||
// </SelectItem>
|
||||
// ))}
|
||||
// </SelectContent>
|
||||
// </Select>
|
||||
// <FormMessage />
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
//
|
||||
// {values.executionMode == "scheduled" ? (
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="scheduledDatetime"
|
||||
// render={({ field }) => (
|
||||
// <FormItem>
|
||||
// <FormLabel>Scheduled date</FormLabel>
|
||||
// <FormControl>
|
||||
// <DateTimePicker value={field.value} onSelect={field.onChange} />
|
||||
// </FormControl>
|
||||
// <FormMessage />
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
// ) : null}
|
||||
//
|
||||
// <Button type="submit">Launch restore</Button>
|
||||
// </Form>
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
@@ -1,8 +1,8 @@
|
||||
import {db} from "@/db";
|
||||
import * as drizzleDb from "@/db";
|
||||
import {and, eq, gte, isNotNull, lt} from "drizzle-orm";
|
||||
import {dispatchNotification} from "@/features/notifications/dispatch";
|
||||
import {EventPayload} from "@/features/notifications/types";
|
||||
import {dispatchNotification} from "@/features/notifications/notifications.dispatch";
|
||||
import {EventPayload} from "@/features/notifications/notifications.types";
|
||||
import {logger} from "@/lib/logger";
|
||||
|
||||
const log = logger.child({module: "tasks/healthcheck"});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {auth, getOrganization} from "@/lib/auth/auth";
|
||||
import {getOrganization} from "@/lib/auth/auth";
|
||||
import {db} from "@/db";
|
||||
import {and, eq} from "drizzle-orm";
|
||||
import * as drizzleDb from "@/db";
|
||||
@@ -59,4 +59,3 @@ export const getOrganizationProjectDatabases = async ({organizationSlug, project
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
+2
-2
@@ -2,9 +2,9 @@
|
||||
|
||||
import {Input} from "@/components/ui/input";
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {Copy, Check, Eye, EyeOff, Terminal, Key, Info} from "lucide-react";
|
||||
import {Copy, Check, Eye, EyeOff} from "lucide-react";
|
||||
import {useState} from "react";
|
||||
import {copyToClipboardWithMeta} from "@/components/wrappers/common/button/copy-button";
|
||||
import {copyToClipboardWithMeta} from "@/components/common/copy-button";
|
||||
import {Card, CardContent, CardDescription, CardHeader, CardTitle} from "@/components/ui/card";
|
||||
|
||||
export type AgentCardKeyProps = {
|
||||
+2
-2
@@ -3,7 +3,7 @@
|
||||
import {useState} from "react";
|
||||
import Link from "next/link";
|
||||
import {Card} from "@/components/ui/card";
|
||||
import {ConnectionIndicator} from "@/components/wrappers/common/connection-indicator";
|
||||
import {ConnectionIndicator} from "@/components/common/connection-indicator";
|
||||
import {formatDateLastContact} from "@/utils/date-formatting";
|
||||
import {AgentWith} from "@/db/schema/08_agent";
|
||||
import {Activity, ChevronRight, Copy, Check, Fingerprint, Server, Database, AlertCircle} from "lucide-react";
|
||||
@@ -11,7 +11,7 @@ import {Badge} from "@/components/ui/badge";
|
||||
import {truncateWords} from "@/utils/text";
|
||||
import {useIsMobile} from "@/hooks/use-mobile";
|
||||
import {Tooltip, TooltipContent, TooltipTrigger} from "@/components/ui/tooltip";
|
||||
import {useAgentUpdateCheck} from "@/features/agents/hooks/use-agent-update-check";
|
||||
import {useAgentUpdateCheck} from "@/features/agents/use-agent-update-check";
|
||||
|
||||
export type agentCardProps = {
|
||||
data: AgentWith;
|
||||
+4
-4
@@ -3,7 +3,7 @@
|
||||
import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card";
|
||||
import {Server} from "lucide-react";
|
||||
import {formatDateLastContact} from "@/utils/date-formatting";
|
||||
import {AgentCardKey} from "@/components/wrappers/dashboard/agent/agent-card-key/agent-card-key";
|
||||
import {AgentCardKey} from "@/features/agents/agent-card-key";
|
||||
import {AgentWithDatabases} from "@/db/schema/08_agent";
|
||||
import {useQuery} from "@tanstack/react-query";
|
||||
import {getAgentAction} from "@/features/agents/agents.action";
|
||||
@@ -15,9 +15,9 @@ import {
|
||||
} from "@/components/ui/accordion";
|
||||
import {Separator} from "@/components/ui/separator";
|
||||
import {Badge} from "@/components/ui/badge";
|
||||
import {CardsWithPagination} from "@/components/wrappers/common/cards-with-pagination";
|
||||
import {AgentDatabaseCard} from "@/components/wrappers/dashboard/agent/agent-database-card";
|
||||
import {HealthCheckGraph} from "@/components/wrappers/dashboard/health/heath-grid";
|
||||
import {CardsWithPagination} from "@/components/common/cards-with-pagination";
|
||||
import {AgentDatabaseCard} from "@/features/agents/agent-database-card";
|
||||
import {HealthCheckGraph} from "@/features/database/health-grid";
|
||||
import {HealthcheckLog} from "@/db/schema/15_healthcheck-log";
|
||||
|
||||
type AgentContentPageProps = {
|
||||
@@ -0,0 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import {Database} from "@/db/schema/07_database";
|
||||
import {DatabaseCard} from "@/components/common/database-card";
|
||||
|
||||
export type agentDatabaseCardProps = {
|
||||
data: Database;
|
||||
};
|
||||
|
||||
export const AgentDatabaseCard = (props: agentDatabaseCardProps) => {
|
||||
const {data: database} = props;
|
||||
|
||||
return <DatabaseCard withDetails={false} data={database}/>;
|
||||
};
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
import {ColumnDef} from "@tanstack/react-table"
|
||||
import {Database} from "@/db/schema/07_database"
|
||||
import Image from "next/image"
|
||||
import {ConnectionIndicator} from "@/components/wrappers/common/connection-indicator"
|
||||
import {ConnectionIndicator} from "@/components/common/connection-indicator"
|
||||
import {formatDateLastContact} from "@/utils/date-formatting"
|
||||
import Link from "next/link"
|
||||
import {Button} from "@/components/ui/button"
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import {Trash2} from "lucide-react";
|
||||
import {ButtonWithConfirm} from "@/components/wrappers/common/button/button-with-confirm";
|
||||
import {ButtonWithConfirm} from "@/components/common/button-with-confirm";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {toast} from "sonner";
|
||||
import {deleteAgentAction} from "@/components/wrappers/dashboard/agent/button-delete-agent/delete-agent.action";
|
||||
import {deleteAgentAction} from "@/features/agents/agent-delete.action";
|
||||
import {useIsMobile} from "@/hooks/use-mobile";
|
||||
|
||||
export type ButtonDeleteAgentProps = {
|
||||
+3
-3
@@ -7,16 +7,16 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import {AgentForm} from "@/features/agents/components/agent.form";
|
||||
import {AgentForm} from "@/features/agents/agent-form";
|
||||
import {Button, buttonVariants} from "@/components/ui/button";
|
||||
import {Plus} from "lucide-react";
|
||||
import {GearIcon} from "@radix-ui/react-icons";
|
||||
import {EmptyStatePlaceholder} from "@/components/wrappers/common/empty-state-placeholder";
|
||||
import {EmptyStatePlaceholder} from "@/components/common/empty-state-placeholder";
|
||||
import {useState} from "react";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {OrganizationWithMembers} from "@/db/schema/03_organization";
|
||||
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs";
|
||||
import {AgentOrganisationForm} from "@/features/agents/components/agent-organizations.form";
|
||||
import {AgentOrganisationForm} from "@/features/agents/agent-organizations.form";
|
||||
import {AgentWith} from "@/db/schema/08_agent";
|
||||
|
||||
type AgentDialogProps = {
|
||||
+1
-2
@@ -5,9 +5,8 @@ import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogT
|
||||
|
||||
import { generateEdgeKey } from "@/utils/edge_key";
|
||||
import { PropsWithChildren } from "react";
|
||||
import { CopyButton } from "@/components/wrappers/common/button/copy-button";
|
||||
import { getServerUrl } from "@/utils/get-server-url";
|
||||
import { CodeSnippet } from "@/components/wrappers/code-snippet/code-snippet";
|
||||
import { CodeSnippet } from "@/components/common/code-snippet";
|
||||
import {Agent} from "@/db/schema/08_agent";
|
||||
|
||||
export type agentRegistrationDialogProps = PropsWithChildren<{
|
||||
+4
-4
@@ -3,13 +3,13 @@
|
||||
import {useRouter} from "next/navigation";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {Form, FormControl, FormField, FormItem, useZodForm} from "@/components/ui/form";
|
||||
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading";
|
||||
import {ButtonWithLoading} from "@/components/common/button-with-loading";
|
||||
import {OrganizationWithMembers} from "@/db/schema/03_organization";
|
||||
import {MultiSelect} from "@/components/wrappers/common/multiselect/multi-select";
|
||||
import {MultiSelect} from "@/components/common/multi-select";
|
||||
import {toast} from "sonner";
|
||||
import {AgentWith} from "@/db/schema/08_agent";
|
||||
import {AgentOrganizationSchema, AgentOrganizationType} from "@/features/agents/components/agent-organizations.schema";
|
||||
import {updateAgentOrganizationsAction} from "@/features/agents/components/agent-organizations.action";
|
||||
import {AgentOrganizationSchema, AgentOrganizationType} from "@/features/agents/agent-organizations.schema";
|
||||
import {updateAgentOrganizationsAction} from "@/features/agents/agent-organizations.action";
|
||||
|
||||
|
||||
type AgentOrganisationFormProps = {
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import {useQuery} from "@tanstack/react-query";
|
||||
import {getNewAgentRelease} from "@/features/updates/services/github";
|
||||
import {getNewAgentRelease} from "@/lib/github-releases";
|
||||
|
||||
export const useAgentUpdateCheck = (currentVersion?: string | null) => {
|
||||
const {data: newRelease, isLoading} = useQuery({
|
||||
+1
-1
@@ -7,7 +7,7 @@ import { FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm }
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Form } from "@/components/ui/form";
|
||||
import { requestPasswordReset } from "@/lib/auth/auth-client";
|
||||
import { ButtonWithLoading } from "@/components/wrappers/common/button/button-with-loading";
|
||||
import { ButtonWithLoading } from "@/components/common/button-with-loading";
|
||||
import Link from "next/link";
|
||||
import { ForgotPasswordSchema, ForgotPasswordType } from "./forgot-password-form.schema";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
import { toast } from "sonner";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import TwoFactorForm from "@/components/wrappers/dashboard/profile/form/2fa-form";
|
||||
import TwoFactorForm from "@/features/profile/2fa-form";
|
||||
|
||||
export const GuardForm = () => {
|
||||
const router = useRouter();
|
||||
+2
-2
@@ -19,8 +19,8 @@ import { PasswordInput } from "@/components/ui/password-input";
|
||||
import {
|
||||
LoginSchema,
|
||||
type LoginType,
|
||||
} from "@/components/wrappers/auth/login/login-form/login-form.schema";
|
||||
import { ButtonWithLoading } from "@/components/wrappers/common/button/button-with-loading";
|
||||
} from "@/features/auth/login-form.schema";
|
||||
import { ButtonWithLoading } from "@/components/common/button-with-loading";
|
||||
import { signIn } from "@/lib/auth/auth-client";
|
||||
|
||||
export type loginFormProps = {
|
||||
+1
-1
@@ -14,7 +14,7 @@ import {
|
||||
import {
|
||||
RegisterSchema,
|
||||
RegisterType,
|
||||
} from "@/components/wrappers/auth/register/register-form/register-form.schema";
|
||||
} from "@/features/auth/register-form.schema";
|
||||
import { signUp } from "@/lib/auth/auth-client";
|
||||
import { useZodForm } from "@/components/ui/form";
|
||||
import { CardAuth } from "@/features/layout/card-auth";
|
||||
+1
-1
@@ -5,7 +5,7 @@ import {toast} from "sonner";
|
||||
|
||||
import {FormControl, FormField, FormItem, FormLabel, useZodForm} from "@/components/ui/form";
|
||||
import {Form} from "@/components/ui/form";
|
||||
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading";
|
||||
import {ButtonWithLoading} from "@/components/common/button-with-loading";
|
||||
import Link from "next/link";
|
||||
import {ResetPasswordSchema, ResetPasswordType} from "./reset-password-form.schema";
|
||||
import {PasswordStrengthInput} from "@/components/ui/password-input-indicator";
|
||||
+1
-4
@@ -1,8 +1,5 @@
|
||||
import {z} from "zod";
|
||||
|
||||
const passwordRegex = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-])/;
|
||||
|
||||
const zPassword = () => z.string().min(8, {message: "New Password too short"}).regex(passwordRegex, {message: "New Password too weak"});
|
||||
import {zPassword} from "@/lib/zod";
|
||||
|
||||
export const ResetPasswordSchema = z
|
||||
.object({
|
||||
+3
-3
@@ -12,11 +12,11 @@ import {useIsMobile} from "@/hooks/use-mobile";
|
||||
import {useEffect, useState} from "react";
|
||||
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs";
|
||||
import {StorageChannelWith} from "@/db/schema/12_storage-channel";
|
||||
import {ChannelForm} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/channel-form";
|
||||
import {ChannelKind, getChannelTextBasedOnKind} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
|
||||
import {ChannelForm} from "@/features/channel/channel-form";
|
||||
import {ChannelKind, getChannelTextBasedOnKind} from "@/features/channel/channels-helpers";
|
||||
import {
|
||||
ChannelOrganisationForm
|
||||
} from "@/components/wrappers/dashboard/admin/channels/organization/channels-organization-form";
|
||||
} from "@/features/organizations/channels-organization-form";
|
||||
|
||||
|
||||
type ChannelAddModalProps = {
|
||||
+3
-3
@@ -9,11 +9,11 @@ import {useIsMobile} from "@/hooks/use-mobile";
|
||||
import {StorageChannelWith} from "@/db/schema/12_storage-channel";
|
||||
import {
|
||||
EditChannelButton
|
||||
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-card/button-edit-channel";
|
||||
import {ChannelKind, getChannelIcon} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
|
||||
} from "@/features/channel/channel-edit-button";
|
||||
import {ChannelKind, getChannelIcon} from "@/features/channel/channels-helpers";
|
||||
import {
|
||||
DeleteChannelButton
|
||||
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-card/button-delete-channel";
|
||||
} from "@/features/channel/channel-delete-button";
|
||||
|
||||
export type ChannelCardProps = {
|
||||
data: NotificationChannelWith | StorageChannelWith;
|
||||
+4
-4
@@ -1,16 +1,16 @@
|
||||
"use client";
|
||||
import {ButtonWithConfirm} from "@/components/wrappers/common/button/button-with-confirm";
|
||||
import {ButtonWithConfirm} from "@/components/common/button-with-confirm";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {Trash2} from "lucide-react";
|
||||
import {
|
||||
removeNotificationChannelAction,
|
||||
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/action";
|
||||
} from "@/features/channel/notifications/channel.action";
|
||||
import {toast} from "sonner";
|
||||
import {ChannelKind, getChannelTextBasedOnKind} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
|
||||
import {ChannelKind, getChannelTextBasedOnKind} from "@/features/channel/channels-helpers";
|
||||
import {
|
||||
removeStorageChannelAction
|
||||
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/storages/action";
|
||||
} from "@/features/channel/storages/channel.action";
|
||||
|
||||
export type DeleteChannelButtonProps = {
|
||||
channelId: string;
|
||||
+6
-6
@@ -7,14 +7,14 @@ import {toast} from "sonner";
|
||||
import {useState} from "react";
|
||||
import {OrganizationWithMembers} from "@/db/schema/03_organization";
|
||||
import {StorageChannelWith} from "@/db/schema/12_storage-channel";
|
||||
import {ChannelKind} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
|
||||
import {ChannelKind} from "@/features/channel/channels-helpers";
|
||||
import {
|
||||
updateNotificationChannelAction
|
||||
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/action";
|
||||
import {ChannelAddEditModal} from "@/components/wrappers/dashboard/admin/channels/channel/channel-add-edit-modal";
|
||||
} from "@/features/channel/notifications/channel.action";
|
||||
import {ChannelAddEditModal} from "@/features/channel/channel-add-edit-modal";
|
||||
import {
|
||||
updateStorageChannelAction
|
||||
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/storages/action";
|
||||
} from "@/features/channel/storages/channel.action";
|
||||
|
||||
export type EditChannelButtonProps = {
|
||||
channel: NotificationChannelWith | StorageChannelWith;
|
||||
@@ -52,10 +52,10 @@ export const EditChannelButton = ({
|
||||
|
||||
let result: any;
|
||||
if (kind == "notification") {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error — payload type varies between notification and storage
|
||||
result = await updateNotificationChannelAction(payload)
|
||||
} else if (kind == "storage") {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error — payload type varies between notification and storage
|
||||
result = await updateStorageChannelAction(payload)
|
||||
} else {
|
||||
toast.error("An error occurred while updating storage channel")
|
||||
+11
-11
@@ -1,15 +1,15 @@
|
||||
import {z} from "zod";
|
||||
import {SlackChannelConfigSchema} from "./providers/notifications/forms/slack.schema";
|
||||
import {SmtpChannelConfigSchema} from "./providers/notifications/forms/smtp.schema";
|
||||
import {DiscordChannelConfigSchema} from "./providers/notifications/forms/discord.schema";
|
||||
import {TelegramChannelConfigSchema} from "./providers/notifications/forms/telegram.schema";
|
||||
import {GotifyChannelConfigSchema} from "./providers/notifications/forms/gotify.schema";
|
||||
import {NtfyChannelConfigSchema} from "./providers/notifications/forms/ntfy.schema";
|
||||
import {WebhookChannelConfigSchema} from "./providers/notifications/forms/webhook.schema";
|
||||
import {NextcloudChannelConfigSchema} from "./providers/notifications/forms/nextcloud.schema";
|
||||
import {S3ChannelConfigSchema} from "./providers/storages/forms/s3.schema";
|
||||
import {GoogleDriveChannelConfigSchema} from "./providers/storages/forms/google-drive.schema";
|
||||
import {LocalChannelConfigSchema} from "./providers/storages/forms/local.schema";
|
||||
import {SlackChannelConfigSchema} from "@/features/channel/notifications/slack.schema";
|
||||
import {SmtpChannelConfigSchema} from "@/features/channel/notifications/smtp.schema";
|
||||
import {DiscordChannelConfigSchema} from "@/features/channel/notifications/discord.schema";
|
||||
import {TelegramChannelConfigSchema} from "@/features/channel/notifications/telegram.schema";
|
||||
import {GotifyChannelConfigSchema} from "@/features/channel/notifications/gotify.schema";
|
||||
import {NtfyChannelConfigSchema} from "@/features/channel/notifications/ntfy.schema";
|
||||
import {WebhookChannelConfigSchema} from "@/features/channel/notifications/webhook.schema";
|
||||
import {NextcloudChannelConfigSchema} from "@/features/channel/notifications/nextcloud.schema";
|
||||
import {S3ChannelConfigSchema} from "@/features/channel/storages/s3.schema";
|
||||
import {GoogleDriveChannelConfigSchema} from "@/features/channel/storages/google-drive/google-drive.schema";
|
||||
import {LocalChannelConfigSchema} from "@/features/channel/storages/local.schema";
|
||||
|
||||
|
||||
const BaseChannelFormSchema = z.object({
|
||||
+11
-11
@@ -3,12 +3,12 @@
|
||||
import {useRouter} from "next/navigation";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {Form, FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm} from "@/components/ui/form";
|
||||
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading";
|
||||
import {ButtonWithLoading} from "@/components/common/button-with-loading";
|
||||
import {Input} from "@/components/ui/input";
|
||||
|
||||
import {
|
||||
addStorageChannelAction, updateStorageChannelAction
|
||||
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/storages/action";
|
||||
} from "@/features/channel/storages/channel.action";
|
||||
import {toast} from "sonner";
|
||||
import {OrganizationWithMembers} from "@/db/schema/03_organization";
|
||||
import {Button} from "@/components/ui/button";
|
||||
@@ -20,19 +20,19 @@ import {ArrowLeft} from "lucide-react";
|
||||
import {StorageChannelWith} from "@/db/schema/12_storage-channel";
|
||||
import {
|
||||
NotificationChannelFormSchema, NotificationChannelFormType, StorageChannelFormSchema, StorageChannelFormType
|
||||
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/channel-form.schema";
|
||||
} from "@/features/channel/channel-form.schema";
|
||||
import {
|
||||
ChannelKind,
|
||||
renderChannelForm
|
||||
} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
|
||||
import {storageProviders} from "@/components/wrappers/dashboard/admin/channels/helpers/storage";
|
||||
import {notificationProviders} from "@/components/wrappers/dashboard/admin/channels/helpers/notification";
|
||||
} from "@/features/channel/channels-helpers";
|
||||
import {storageProviders} from "@/features/channel/channels-storage-helper";
|
||||
import {notificationProviders} from "@/features/channel/channels-notification-helper";
|
||||
import {
|
||||
ChannelTestButton
|
||||
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/channel-test-button";
|
||||
} from "@/features/channel/channel-test-button";
|
||||
import {
|
||||
addNotificationChannelAction, updateNotificationChannelAction
|
||||
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/action";
|
||||
} from "@/features/channel/notifications/channel.action";
|
||||
|
||||
|
||||
type NotifierFormProps = {
|
||||
@@ -50,7 +50,7 @@ export const ChannelForm = ({onSuccessAction, organization, defaultValues, kind}
|
||||
|
||||
const form = useZodForm({
|
||||
schema: kind == "notification" ? NotificationChannelFormSchema : StorageChannelFormSchema,
|
||||
// @ts-ignore
|
||||
// @ts-expect-error — defaultValues type is a union of notification/storage channel types
|
||||
defaultValues: {...defaultValues},
|
||||
});
|
||||
|
||||
@@ -71,10 +71,10 @@ export const ChannelForm = ({onSuccessAction, organization, defaultValues, kind}
|
||||
let result: any;
|
||||
|
||||
if (kind === "notification") {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error — payload type varies between notification and storage
|
||||
result = isCreate ? await addNotificationChannelAction(payload) : await updateNotificationChannelAction(payload);
|
||||
} else if (kind === "storage") {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error — payload type varies between notification and storage
|
||||
result = isCreate ? await addStorageChannelAction(payload) : await updateStorageChannelAction(payload);
|
||||
} else {
|
||||
toast.error("An error occurred");
|
||||
+5
-6
@@ -6,14 +6,14 @@ import {toast} from "sonner";
|
||||
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {NotificationChannel} from "@/db/schema/09_notification-channel";
|
||||
import {dispatchNotification} from "@/features/notifications/dispatch";
|
||||
import {EventPayload} from "@/features/notifications/types";
|
||||
import {dispatchNotification} from "@/features/notifications/notifications.dispatch";
|
||||
import {EventPayload} from "@/features/notifications/notifications.types";
|
||||
import {useIsMobile} from "@/hooks/use-mobile";
|
||||
import {cn} from "@/lib/utils";
|
||||
import {StorageChannel} from "@/db/schema/12_storage-channel";
|
||||
import {ChannelKind} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
|
||||
import type {StorageInput} from "@/features/storages/types";
|
||||
import {dispatchStorage} from "@/features/storages/dispatch";
|
||||
import {ChannelKind} from "@/features/channel/channels-helpers";
|
||||
import type {StorageInput} from "@/features/storages/storages.types";
|
||||
import {dispatchStorage} from "@/features/storages/storages.dispatch";
|
||||
|
||||
|
||||
type NotifierTestChannelButtonProps = {
|
||||
@@ -50,7 +50,6 @@ export const ChannelTestButton = ({channel, organizationId, kind}: NotifierTestC
|
||||
if (result.success) {
|
||||
toast.success("Successfully connected to storage channel");
|
||||
} else {
|
||||
console.error(result);
|
||||
const responseText = result.response ? `(${result.response})` : "";
|
||||
toast.error(`An error occurred while testing the storage channel, check your configuration ${responseText}`);
|
||||
}
|
||||
+12
-12
@@ -1,40 +1,40 @@
|
||||
import {UseFormReturn} from "react-hook-form";
|
||||
import {
|
||||
NotifierSmtpForm
|
||||
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/forms/smtp.form";
|
||||
} from "@/features/channel/notifications/smtp.form";
|
||||
import {
|
||||
NotifierSlackForm
|
||||
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/forms/slack.form";
|
||||
} from "@/features/channel/notifications/slack.form";
|
||||
import {
|
||||
NotifierDiscordForm
|
||||
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/forms/discord.form";
|
||||
} from "@/features/channel/notifications/discord.form";
|
||||
import {
|
||||
NotifierTelegramForm
|
||||
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/forms/telegram.form";
|
||||
} from "@/features/channel/notifications/telegram.form";
|
||||
import {
|
||||
NotifierGotifyForm
|
||||
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/forms/gotify.form";
|
||||
} from "@/features/channel/notifications/gotify.form";
|
||||
import {
|
||||
NotifierNtfyForm
|
||||
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/forms/ntfy.form";
|
||||
} from "@/features/channel/notifications/ntfy.form";
|
||||
import {
|
||||
NotifierWebhookForm
|
||||
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/forms/webhook.form";
|
||||
} from "@/features/channel/notifications/webhook.form";
|
||||
import {
|
||||
NotifierNextcloudForm
|
||||
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/forms/nextcloud.form";
|
||||
} from "@/features/channel/notifications/nextcloud.form";
|
||||
import {
|
||||
notificationProviders,
|
||||
} from "@/components/wrappers/dashboard/admin/channels/helpers/notification";
|
||||
import {storageProviders} from "@/components/wrappers/dashboard/admin/channels/helpers/storage";
|
||||
} from "@/features/channel/channels-notification-helper";
|
||||
import {storageProviders} from "@/features/channel/channels-storage-helper";
|
||||
import {ForwardRefExoticComponent, JSX, RefAttributes, SVGProps} from "react";
|
||||
import {LucideProps} from "lucide-react";
|
||||
import {
|
||||
StorageS3Form
|
||||
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/storages/forms/s3.form";
|
||||
} from "@/features/channel/storages/s3.form";
|
||||
import {
|
||||
StorageGoogleDriveForm
|
||||
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/storages/forms/google-drive.form";
|
||||
} from "@/features/channel/storages/google-drive/google-drive.form";
|
||||
|
||||
export type ChannelKind = "notification" | "storage";
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import {Mail} from "lucide-react";
|
||||
import type { SVGProps } from 'react';
|
||||
import {ProviderIconTypes} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
|
||||
import {ProviderIconTypes} from "@/features/channel/channels-helpers";
|
||||
|
||||
|
||||
|
||||
+5
-5
@@ -1,16 +1,16 @@
|
||||
"use client";
|
||||
import { NotificationChannelWith } from "@/db/schema/09_notification-channel";
|
||||
import { CardsWithPagination } from "@/components/wrappers/common/cards-with-pagination";
|
||||
import { CardsWithPagination } from "@/components/common/cards-with-pagination";
|
||||
import { useState } from "react";
|
||||
import { EmptyStatePlaceholder } from "@/components/wrappers/common/empty-state-placeholder";
|
||||
import { EmptyStatePlaceholder } from "@/components/common/empty-state-placeholder";
|
||||
import { OrganizationWithMembers } from "@/db/schema/03_organization";
|
||||
import { StorageChannelWith } from "@/db/schema/12_storage-channel";
|
||||
import { ChannelCard } from "@/components/wrappers/dashboard/admin/channels/channel/channel-card/channel-card";
|
||||
import { ChannelAddEditModal } from "@/components/wrappers/dashboard/admin/channels/channel/channel-add-edit-modal";
|
||||
import { ChannelCard } from "@/features/channel/channel-card";
|
||||
import { ChannelAddEditModal } from "@/features/channel/channel-add-edit-modal";
|
||||
import {
|
||||
ChannelKind,
|
||||
getChannelTextBasedOnKind,
|
||||
} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
|
||||
} from "@/features/channel/channels-helpers";
|
||||
|
||||
type ChannelsSectionProps = {
|
||||
channels: NotificationChannelWith[] | StorageChannelWith[];
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import {Server} from "lucide-react";
|
||||
import type {SVGProps} from "react";
|
||||
import {ProviderIconTypes} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
|
||||
import {ProviderIconTypes} from "@/features/channel/channels-helpers";
|
||||
|
||||
export const storageProviders: ProviderIconTypes[] = [
|
||||
{value: "local", label: "Local", icon: Server},
|
||||
+7
-7
@@ -10,7 +10,7 @@ import {and, eq} from "drizzle-orm";
|
||||
import {withUpdatedAt} from "@/db/utils";
|
||||
import {
|
||||
NotificationChannelFormSchema
|
||||
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/channel-form.schema";
|
||||
} from "@/features/channel/channel-form.schema";
|
||||
|
||||
|
||||
export const addNotificationChannelAction = userAction.schema(
|
||||
@@ -50,8 +50,8 @@ export const addNotificationChannelAction = userAction.schema(
|
||||
messageParams: {notificationChannelId: channel.id},
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
} catch (_error) {
|
||||
const error = _error;
|
||||
return {
|
||||
success: false,
|
||||
actionError: {
|
||||
@@ -111,8 +111,8 @@ export const removeNotificationChannelAction = userAction.schema(
|
||||
messageParams: {notificationChannelId: notificationChannelId},
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
} catch (_error) {
|
||||
const error = _error;
|
||||
return {
|
||||
success: false,
|
||||
actionError: {
|
||||
@@ -157,8 +157,8 @@ export const updateNotificationChannelAction = userAction.schema(
|
||||
messageParams: {id: channel.id},
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
} catch (_error) {
|
||||
const error = _error;
|
||||
return {
|
||||
success: false,
|
||||
actionError: {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import type {EventPayload, DispatchResult} from '../types';
|
||||
import type {EventPayload, DispatchResult} from '@/features/notifications/notifications.types';
|
||||
|
||||
export async function sendDiscord(
|
||||
config: { discordWebhook: string },
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import type {EventPayload, DispatchResult} from '../types';
|
||||
import type {EventPayload, DispatchResult} from '@/features/notifications/notifications.types';
|
||||
|
||||
export async function sendGotify(
|
||||
config: { gotifyServerUrl: string; gotifyAppToken: string },
|
||||
+7
-7
@@ -1,13 +1,13 @@
|
||||
"use server"
|
||||
import type {ProviderKind, EventPayload, DispatchResult} from '../types';
|
||||
import type {ProviderKind, EventPayload, DispatchResult} from '@/features/notifications/notifications.types';
|
||||
import {sendSlack} from './slack';
|
||||
import {sendSmtp} from './smtp';
|
||||
import {sendDiscord} from "@/features/notifications/providers/discord";
|
||||
import {sendTelegram} from "@/features/notifications/providers/telegram";
|
||||
import {sendGotify} from "@/features/notifications/providers/gotify";
|
||||
import {sendNtfy} from "@/features/notifications/providers/ntfy";
|
||||
import {sendWebhook} from "@/features/notifications/providers/webhook";
|
||||
import {sendNextcloud} from "@/features/notifications/providers/nextcloud";
|
||||
import {sendDiscord} from "@/features/channel/notifications/discord";
|
||||
import {sendTelegram} from "@/features/channel/notifications/telegram";
|
||||
import {sendGotify} from "@/features/channel/notifications/gotify";
|
||||
import {sendNtfy} from "@/features/channel/notifications/ntfy";
|
||||
import {sendWebhook} from "@/features/channel/notifications/webhook";
|
||||
import {sendNextcloud} from "@/features/channel/notifications/nextcloud";
|
||||
|
||||
const handlers: Record<
|
||||
ProviderKind,
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { createHmac, randomBytes } from "crypto";
|
||||
import type { EventPayload, DispatchResult } from "../types";
|
||||
import type { EventPayload, DispatchResult } from '@/features/notifications/notifications.types';
|
||||
|
||||
type NextcloudConfig = {
|
||||
nextcloudUrl: string;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import type {EventPayload, DispatchResult} from '../types';
|
||||
import type {EventPayload, DispatchResult} from '@/features/notifications/notifications.types';
|
||||
|
||||
const getPriority = (level?: string): number => {
|
||||
switch (level) {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import type {EventPayload, DispatchResult} from '../types';
|
||||
import type {EventPayload, DispatchResult} from '@/features/notifications/notifications.types';
|
||||
|
||||
export async function sendSlack(
|
||||
config: { slackWebhook: string },
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user