diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 564cff9..03d9549 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -1,10 +1,6 @@
-
-
-
-
-
+
\ No newline at end of file
diff --git a/.zed/settings.json b/.zed/settings.json
new file mode 100644
index 0000000..162eada
--- /dev/null
+++ b/.zed/settings.json
@@ -0,0 +1,29 @@
+// Folder-specific settings
+//
+// For a full list of overridable settings, and general information on folder-specific settings,
+// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
+{
+ "languages": {
+ "JavaScript": {
+ "format_on_save": "on",
+ "formatter": ["prettier"],
+ "code_actions_on_format": {
+ "source.fixAll.eslint": true
+ }
+ },
+ "TypeScript": {
+ "format_on_save": "on",
+ "formatter": ["prettier"],
+ "code_actions_on_format": {
+ "source.fixAll.eslint": true
+ }
+ },
+ "TSX": {
+ "format_on_save": "on",
+ "formatter": ["prettier"],
+ "code_actions_on_format": {
+ "source.fixAll.eslint": true
+ }
+ }
+ }
+}
diff --git a/nx.json b/nx.json
index 62c5f29..5b63bdd 100644
--- a/nx.json
+++ b/nx.json
@@ -23,8 +23,8 @@
"plugins": [
{
"plugin": "@nx/vite/plugin",
+ "buildTargetName": "vite:build",
"options": {
- "buildTargetName": "vite:build",
"testTargetName": "test",
"serveTargetName": "serve",
"devTargetName": "vite:dev",
diff --git a/package.json b/package.json
index d9bf03c..6d760c4 100644
--- a/package.json
+++ b/package.json
@@ -21,14 +21,16 @@
"compile": "yarn build"
},
"devDependencies": {
- "@nx/eslint": "20.4.1",
- "@nx/vite": "20.4.1",
- "@nx/web": "20.4.1",
- "@vitest/ui": "^1.3.1",
- "nx": "20.4.1",
- "prettier": "^3.4.2",
- "typescript": "^5.7.3",
- "vite": "^5.0.0",
- "vitest": "^1.3.1"
+ "@nx/eslint": "^22.0.4",
+ "@nx/vite": "^22.0.4",
+ "@nx/web": "^22.0.4",
+ "@vitest/ui": "^4.0.10",
+ "eslint-plugin-import": "^2.32.0",
+ "eslint-plugin-no-relative-import-paths": "^1.6.1",
+ "nx": "^22.0.4",
+ "prettier": "^3.6.2",
+ "typescript": "^5.9.3",
+ "vite": "^7.2.2",
+ "vitest": "^4.0.10"
}
}
diff --git a/packages/executor/Dockerfile b/packages/executor/Dockerfile
index 02faa3b..05289b4 100644
--- a/packages/executor/Dockerfile
+++ b/packages/executor/Dockerfile
@@ -13,7 +13,7 @@ COPY packages/storage ./packages/storage
COPY packages/executor ./packages/executor
ENV NX_DAEMON=false
-RUN yarn nx run executor:build
+RUN yarn nx run build extension-a11y-checker-executor
FROM --platform=amd64 node:22-slim
diff --git a/packages/executor/package.json b/packages/executor/package.json
index 4904eaa..c66f66f 100644
--- a/packages/executor/package.json
+++ b/packages/executor/package.json
@@ -16,22 +16,22 @@
"format:check": "prettier $@ '**/*.{ts,tsx,yaml,yml,json,md,mdx}' --check"
},
"dependencies": {
- "extension-a11y-checker-storage": "workspace:*",
- "lighthouse": "^12.3.0",
- "node-cron": "^3.0.3",
- "pa11y": "^8.0.0",
- "pino": "^9.6.0",
- "pino-pretty": "^13.0.0",
- "puppeteer": "^24.2.1",
+ "extension-a11y-checker-storage": "workspace:^",
+ "lighthouse": "^13.0.1",
+ "node-cron": "^4.2.1",
+ "pa11y": "^9.0.1",
+ "pino": "^10.1.0",
+ "pino-pretty": "^13.1.2",
+ "puppeteer": "^24.30.0",
"reflect-metadata": "^0.2.2"
},
"devDependencies": {
- "@eslint/eslintrc": "^3",
- "@types/node": "^20",
+ "@eslint/eslintrc": "^3.3.1",
+ "@types/node": "^24.10.1",
"@types/node-cron": "^3.0.11",
- "eslint": "^9",
- "nodemon": "^3.1.9",
- "typescript": "^5"
+ "eslint": "^9.39.1",
+ "nodemon": "^3.1.11",
+ "typescript": "^5.9.3"
},
"packageManager": "yarn@4.6.0"
}
diff --git a/packages/storage/package.json b/packages/storage/package.json
index 7b46c9c..0381079 100644
--- a/packages/storage/package.json
+++ b/packages/storage/package.json
@@ -26,16 +26,16 @@
"format:check": "prettier $@ '**/*.{ts,tsx,yaml,yml,json,md,mdx}' --check"
},
"dependencies": {
- "@typegoose/typegoose": "^12.11.0",
- "cron-parser": "^4.9.0",
- "mongodb": "^6.12.0",
- "mongoose": "^8.10.0"
+ "@typegoose/typegoose": "^12.20.0",
+ "cron-parser": "^5.4.0",
+ "mongodb": "^7.0.0",
+ "mongoose": "^8.20.0"
},
"devDependencies": {
- "@eslint/eslintrc": "^3",
- "@types/node": "^20",
- "eslint": "^9",
- "typescript": "^5"
+ "@eslint/eslintrc": "^3.3.1",
+ "@types/node": "^24.10.1",
+ "eslint": "^9.39.1",
+ "typescript": "^5.9.3"
},
"packageManager": "yarn@4.6.0"
}
diff --git a/packages/storage/src/context/context.model.ts b/packages/storage/src/context/context.model.ts
index 704f5ab..63f935f 100644
--- a/packages/storage/src/context/context.model.ts
+++ b/packages/storage/src/context/context.model.ts
@@ -1,12 +1,16 @@
import { prop, modelOptions } from "@typegoose/typegoose";
-import { ObjectId } from "mongoose";
-import { getModel } from "../lib/mongoose.js";
+import { getModel, serializeObjectWithIds } from "../lib/mongoose.js";
import { ReturnModelType } from "@typegoose/typegoose/lib/types";
import { ScanProfileModel } from "../scanProfile/scanProfile.model.js";
-import { ScanModel } from "../scan/scan.model.js";
@modelOptions({
- schemaOptions: { versionKey: false, collection: "contexts" },
+ schemaOptions: {
+ versionKey: false,
+ collection: "contexts",
+ toJSON: {
+ transform: (doc, ret) => serializeObjectWithIds(ret),
+ },
+ },
options: { automaticName: false },
})
export class Context {
@@ -29,7 +33,7 @@ export class Context {
this: ReturnModelType,
contextId: string,
) {
- return this.updateOne(
+ return this.findOneAndUpdate(
{ _id: contextId },
{
$setOnInsert: { _id: contextId },
diff --git a/packages/storage/src/index.ts b/packages/storage/src/index.ts
index 516903e..75b6522 100644
--- a/packages/storage/src/index.ts
+++ b/packages/storage/src/index.ts
@@ -4,3 +4,4 @@ export { dbConnect };
export * from "./context/context.model.js";
export * from "./scan/scan.model.js";
export * from "./scanProfile/scanProfile.model.js";
+export { serializeObjectWithIds } from "./lib/mongoose.js";
diff --git a/packages/storage/src/lib/mongoose.ts b/packages/storage/src/lib/mongoose.ts
index 3fc881f..ec5e758 100644
--- a/packages/storage/src/lib/mongoose.ts
+++ b/packages/storage/src/lib/mongoose.ts
@@ -1,4 +1,4 @@
-import mongoose from "mongoose";
+import mongoose, { ObjectId } from "mongoose";
import type {
AnyParamConstructor,
ReturnModelType,
@@ -14,3 +14,49 @@ export function getModel>(
getModelForClass(modelClass, { options: { customName: modelName } })
);
}
+
+export type Serialize = T extends ObjectId
+ ? string
+ : T extends (infer U)[]
+ ? Serialize[]
+ : T extends object
+ ? { [K in keyof T]: Serialize }
+ : T;
+
+function isObjectId(value: unknown): value is ObjectId {
+ return (
+ !!value &&
+ typeof value === "object" &&
+ ((value as any).toHexString instanceof Function ||
+ ((value as any).toString instanceof Function &&
+ (value as any).constructor?.name === "ObjectId"))
+ );
+}
+
+function isPlainObject(value: unknown): value is Record {
+ if (Object.prototype.toString.call(value) !== "[object Object]") return false;
+ const proto = Object.getPrototypeOf(value);
+ return proto === Object.prototype || proto === null;
+}
+
+export function serializeObjectWithIds(obj: T): Serialize {
+ if (obj == null) return obj as Serialize;
+
+ if (isObjectId(obj)) return obj.toString() as Serialize;
+
+ if (Array.isArray(obj)) {
+ return (obj as unknown[]).map(serializeObjectWithIds) as Serialize;
+ }
+
+ if (isPlainObject(obj)) {
+ const result: Record = {};
+ for (const key of Object.keys(obj as Record)) {
+ result[key] = serializeObjectWithIds(
+ (obj as Record)[key],
+ );
+ }
+ return result as Serialize;
+ }
+
+ return obj as Serialize;
+}
diff --git a/packages/storage/src/scan/scan.model.ts b/packages/storage/src/scan/scan.model.ts
index 9972530..1e37802 100644
--- a/packages/storage/src/scan/scan.model.ts
+++ b/packages/storage/src/scan/scan.model.ts
@@ -3,9 +3,16 @@ import { isDocument } from "@typegoose/typegoose";
import { index, modelOptions, prop } from "@typegoose/typegoose";
import { ObjectId } from "mongodb";
import { ScanProfile } from "../scanProfile/scanProfile.model.js";
-import { getModel } from "../lib/mongoose.js";
+import { getModel, serializeObjectWithIds } from "../lib/mongoose.js";
-@modelOptions({ schemaOptions: { _id: false } })
+@modelOptions({
+ schemaOptions: {
+ _id: false,
+ toJSON: {
+ transform: (doc, ret) => serializeObjectWithIds(ret),
+ },
+ },
+})
export class Issue {
@prop({ required: true })
public url: string;
@@ -27,7 +34,13 @@ export class Issue {
}
@modelOptions({
- schemaOptions: { _id: false, suppressReservedKeysWarning: true },
+ schemaOptions: {
+ _id: false,
+ suppressReservedKeysWarning: true,
+ toJSON: {
+ transform: (doc, ret) => serializeObjectWithIds(ret),
+ },
+ },
})
export class Issues {
@prop({ required: true })
@@ -38,7 +51,14 @@ export class Issues {
public notices: number = 0;
}
-@modelOptions({ schemaOptions: { _id: false } })
+@modelOptions({
+ schemaOptions: {
+ _id: false,
+ toJSON: {
+ transform: (doc, ret) => serializeObjectWithIds(ret),
+ },
+ },
+})
export class Page {
@prop({ required: true })
public url: string;
@@ -56,7 +76,13 @@ export class Page {
@index({ status: 1, executionScheduledFor: 1 })
@modelOptions({
- schemaOptions: { collection: "scans", versionKey: false },
+ schemaOptions: {
+ collection: "scans",
+ versionKey: false,
+ toJSON: {
+ transform: (doc, ret) => serializeObjectWithIds(ret),
+ },
+ },
options: { automaticName: false },
})
export class Scan {
diff --git a/packages/storage/src/scanProfile/scanProfile.model.ts b/packages/storage/src/scanProfile/scanProfile.model.ts
index dde2642..dbc6df9 100644
--- a/packages/storage/src/scanProfile/scanProfile.model.ts
+++ b/packages/storage/src/scanProfile/scanProfile.model.ts
@@ -1,9 +1,9 @@
import type { DocumentType, Ref } from "@typegoose/typegoose";
import { modelOptions, prop } from "@typegoose/typegoose";
-import cronParser from "cron-parser";
+import { CronExpressionParser } from "cron-parser";
import { ObjectId } from "mongodb";
import { Context, ContextModel } from "../context/context.model.js";
-import { getModel } from "../lib/mongoose.js";
+import { getModel, serializeObjectWithIds } from "../lib/mongoose.js";
import { ReturnModelType } from "@typegoose/typegoose/lib/types";
import { Scan, ScanModel } from "../scan/scan.model.js";
@@ -17,7 +17,10 @@ class CronSchedule {
schemaOptions: {
collection: "scanprofiles",
versionKey: false,
- toJSON: { virtuals: true },
+ toJSON: {
+ virtuals: true,
+ transform: (doc, ret) => serializeObjectWithIds(ret),
+ },
toObject: { virtuals: true },
},
options: { automaticName: false },
@@ -79,7 +82,7 @@ export class ScanProfile {
match: { completedAt: { $exists: true } },
options: { sort: { completedAt: -1 } },
})
- public lastScan: Scan | null = null;
+ public lastScan: Ref;
@prop({
ref: () => "Scan",
@@ -89,7 +92,7 @@ export class ScanProfile {
match: { status: { $in: ["queued", "running"] } },
options: { sort: { executionScheduledFor: 1 } },
})
- public nextScan: Scan | null = null;
+ public nextScan: Ref;
public static async delete(
this: ReturnModelType,
@@ -107,14 +110,10 @@ export class ScanProfile {
if (!context) {
return null;
}
- const profiles = await ScanProfileModel.find({ context: contextId }).exec();
- await Promise.all(
- profiles.map(async (p) => {
- await p.populate("nextScan");
- await p.populate({ path: "lastScan", select: "-issues" });
- }),
- );
- return profiles;
+ return await ScanProfileModel.find({ context: contextId })
+ .populate("nextScan")
+ .populate({ path: "lastScan", select: "-issues" })
+ .exec();
}
public nextExecution(this: DocumentType) {
@@ -122,8 +121,7 @@ export class ScanProfile {
return null;
}
- return cronParser
- .parseExpression(this.cronSchedule.expression)
+ return CronExpressionParser.parse(this.cronSchedule.expression)
.next()
.toDate();
}
diff --git a/packages/web2/Dockerfile b/packages/web2/Dockerfile
index b017faf..889ca3f 100644
--- a/packages/web2/Dockerfile
+++ b/packages/web2/Dockerfile
@@ -11,7 +11,7 @@ COPY packages/storage ./packages/storage
RUN corepack enable && yarn install
ENV NX_DAEMON=false
-RUN yarn nx run web2:build
+RUN yarn nx run build extension-a11y-checker-web2
FROM node:22-slim
diff --git a/packages/web2/app.config.ts b/packages/web2/app.config.ts
deleted file mode 100644
index 3fe6d31..0000000
--- a/packages/web2/app.config.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { defineConfig } from "@tanstack/react-start/config";
-import tsConfigPaths from "vite-tsconfig-paths";
-
-export default defineConfig({
- server: {
- esbuild: {
- options: {
- minify: false,
- target: "es2022",
- },
- },
- },
- vite: {
- server: {
- allowedHosts: ["host.docker.internal"],
- },
- build: {
- minify: false,
- },
- resolve: {
- alias: {
- // /esm/icons/index.mjs only exports the icons statically, so no separate chunks are created
- "@tabler/icons-react": "@tabler/icons-react/dist/esm/icons/index.mjs",
- },
- },
- plugins: [
- tsConfigPaths({
- projects: ["./tsconfig.json"],
- }),
- ],
- },
-});
diff --git a/packages/web2/app/actions/commons.ts b/packages/web2/app/actions/commons.ts
index a623bdd..c8244c4 100644
--- a/packages/web2/app/actions/commons.ts
+++ b/packages/web2/app/actions/commons.ts
@@ -1,6 +1,6 @@
import { ScanModel, ScanProfileModel } from "extension-a11y-checker-storage";
import { notFound } from "@tanstack/react-router";
-import cronParser from "cron-parser";
+import { CronExpressionParser } from "cron-parser";
export async function assertProfile(profileId: string, contextId?: string) {
const profile = await ScanProfileModel.findById(profileId);
@@ -23,7 +23,7 @@ export async function scheduleScan(
export const validateCron = (cronExpression: string | undefined) => {
if (cronExpression) {
try {
- const interval = cronParser.parseExpression(cronExpression);
+ const interval = CronExpressionParser.parse(cronExpression);
const firstDate = interval.next();
const secondDate = interval.next();
const hoursDiff =
diff --git a/packages/web2/app/actions/middleware.ts b/packages/web2/app/actions/middleware.ts
index 766b730..c2284e4 100644
--- a/packages/web2/app/actions/middleware.ts
+++ b/packages/web2/app/actions/middleware.ts
@@ -2,7 +2,7 @@ import { createMiddleware } from "@tanstack/react-start";
import { notFound } from "@tanstack/react-router";
import { dbConnect, ScanProfileModel } from "extension-a11y-checker-storage";
import { z } from "zod";
-import { getHeader } from "@tanstack/react-start/server";
+import { getRequestHeader } from "@tanstack/react-start/server";
import { getAccessToken, verify } from "@mittwald/ext-bridge/node";
import { getSessionToken } from "@mittwald/ext-bridge/browser";
@@ -18,7 +18,7 @@ const getToken = async (sessionToken: string) => {
return (await getAccessToken(sessionToken, extensionSecret)).publicToken;
};
-export const authenticateMiddleware = createMiddleware({ validateClient: true })
+export const authenticateMiddleware = createMiddleware({ type: "function" })
.client(async ({ next }) => {
const token = await getSessionToken();
return next({
@@ -26,7 +26,7 @@ export const authenticateMiddleware = createMiddleware({ validateClient: true })
});
})
.server(async ({ next }) => {
- const sessionToken = getHeader("x-session-token");
+ const sessionToken = getRequestHeader("x-session-token");
const verifiedToken = await verify(sessionToken!);
const apiToken = await getToken(sessionToken!);
@@ -45,9 +45,9 @@ const contextSchema = z
})
.catchall(z.any());
-export const contextMatchingMiddleware = createMiddleware()
+export const contextMatchingMiddleware = createMiddleware({ type: "function" })
.middleware([authenticateMiddleware])
- .validator(contextSchema)
+ .inputValidator(contextSchema)
.server(async ({ next, context, data: { contextId } }) => {
if (context.contextId !== contextId) {
throw notFound();
@@ -73,18 +73,20 @@ async function assertContextMatching(profileId: string, contextId: string) {
}
}
-export const profileIdAuthorizeMiddleware = createMiddleware()
+export const profileIdAuthorizeMiddleware = createMiddleware({
+ type: "function",
+})
.middleware([dbMiddleware, authenticateMiddleware])
- .validator(profileIdSchema)
+ .inputValidator(profileIdSchema)
.server(async ({ next, context, data: profileId }) => {
const { contextId } = context;
await assertContextMatching(profileId, contextId);
return next();
});
-export const profileAuthorizeMiddleware = createMiddleware()
+export const profileAuthorizeMiddleware = createMiddleware({ type: "function" })
.middleware([dbMiddleware, authenticateMiddleware])
- .validator(profileSchema)
+ .inputValidator(profileSchema)
.server(async ({ next, context, data: { profileId } }) => {
const { contextId } = context;
await assertContextMatching(profileId, contextId);
diff --git a/packages/web2/app/actions/profile.ts b/packages/web2/app/actions/profile.ts
index 0249cc5..bc8a9aa 100644
--- a/packages/web2/app/actions/profile.ts
+++ b/packages/web2/app/actions/profile.ts
@@ -1,7 +1,11 @@
import { createServerFn } from "@tanstack/react-start";
import { z } from "zod";
-import { ScanModel, ScanProfileModel } from "extension-a11y-checker-storage";
-import { Scan, ScanProfile } from "../api/types.ts";
+import {
+ ScanModel,
+ ScanProfileModel,
+ serializeObjectWithIds,
+} from "extension-a11y-checker-storage";
+import { Scan, ScanProfile } from "~/api/types.ts";
import { ObjectId } from "mongodb";
import { notFound } from "@tanstack/react-router";
import {
@@ -12,10 +16,11 @@ import {
profileIdAuthorizeMiddleware,
} from "./middleware.js";
import { scheduleScan, validateCron } from "./commons.js";
+import { isDocument } from "@typegoose/typegoose";
export const getProfiles = createServerFn()
.middleware([dbMiddleware, authenticateMiddleware])
- .validator(z.string())
+ .inputValidator(z.string())
.handler(async ({ data: contextId }) => {
const data = await ScanProfileModel.findForContext(contextId);
if (data === null) {
@@ -23,10 +28,15 @@ export const getProfiles = createServerFn()
}
const profiles = data.map((profileDoc) => {
- const profileObject = profileDoc.toObject();
+ const profileObject = profileDoc.toJSON();
+
+ const issueSummary = isDocument(profileDoc.lastScan)
+ ? profileDoc.lastScan.getIssueSummary()
+ : undefined;
+
return {
...profileObject,
- issueSummary: profileDoc.lastScan?.getIssueSummary(),
+ issueSummary,
} as unknown as ScanProfile;
});
return profiles;
@@ -36,7 +46,7 @@ export const getProfile = createServerFn({
method: "GET",
})
.middleware([dbMiddleware, profileIdAuthorizeMiddleware])
- .validator(z.string())
+ .inputValidator(z.string())
.handler(async ({ data: profileId }) => {
const profile = await ScanProfileModel.findById(profileId).exec();
await profile?.populate("nextScan");
@@ -46,18 +56,19 @@ export const getProfile = createServerFn({
return {
profile: {
- ...profile?.toObject(),
+ ...profile?.toJSON(),
issueSummary: lastScan?.getIssueSummary(),
} as unknown as ScanProfile,
-
- lastScan: lastScan as unknown as Scan | undefined,
- lastSuccessfulScan: lastSuccessfulScan as unknown as Scan | undefined,
+ lastScan: serializeObjectWithIds(lastScan?.toJSON()) as unknown as Scan,
+ lastSuccessfulScan: serializeObjectWithIds(
+ lastSuccessfulScan?.toJSON(),
+ ) as unknown as Scan,
};
});
export const createProfile = createServerFn({ method: "POST" })
.middleware([dbMiddleware, contextMatchingMiddleware])
- .validator(
+ .inputValidator(
z.object({
contextId: z.string(),
domain: z.string(),
@@ -77,7 +88,7 @@ export const createProfile = createServerFn({ method: "POST" })
export const updateProfilePaths = createServerFn({ method: "POST" })
.middleware([dbMiddleware, profileAuthorizeMiddleware])
- .validator(
+ .inputValidator(
z.object({
profileId: z.string(),
paths: z.array(z.string()),
@@ -90,14 +101,14 @@ export const updateProfilePaths = createServerFn({ method: "POST" })
{ new: true },
);
if (!profile) {
- return new Response("Profile not found", { status: 404 });
+ throw notFound({ data: "Profile not found" });
}
return profile.toJSON() as unknown as ScanProfile;
});
export const updateProfileName = createServerFn({ method: "POST" })
.middleware([dbMiddleware, profileAuthorizeMiddleware])
- .validator(
+ .inputValidator(
z.object({
profileId: z.string(),
name: z.string(),
@@ -110,14 +121,14 @@ export const updateProfileName = createServerFn({ method: "POST" })
{ new: true },
);
if (!profile) {
- return new Response("Profile not found", { status: 404 });
+ throw notFound({ data: "Profile not found" });
}
return profile.toJSON() as unknown as ScanProfile;
});
export const updateProfileDomain = createServerFn({ method: "POST" })
.middleware([dbMiddleware, profileAuthorizeMiddleware])
- .validator(
+ .inputValidator(
z.object({
profileId: z.string(),
updateName: z.boolean().optional(),
@@ -137,7 +148,7 @@ export const updateProfileDomain = createServerFn({ method: "POST" })
{ new: true },
);
if (!profile) {
- return new Response("Profile not found", { status: 404 });
+ throw notFound({ data: "Profile not found" });
}
const nextScan = await ScanModel.nextScanOfProfile(profileId);
@@ -150,7 +161,7 @@ export const updateProfileDomain = createServerFn({ method: "POST" })
export const updateProfileCron = createServerFn({ method: "POST" })
.middleware([dbMiddleware, profileAuthorizeMiddleware])
- .validator(
+ .inputValidator(
z.object({
profileId: z.string(),
cronExpression: z.string(),
@@ -159,7 +170,7 @@ export const updateProfileCron = createServerFn({ method: "POST" })
.handler(async ({ data: { profileId, cronExpression } }) => {
const validationResult = validateCron(cronExpression);
if (validationResult !== true) {
- return validationResult;
+ throw validationResult;
}
const cronUpdateSet = cronExpression
@@ -176,7 +187,7 @@ export const updateProfileCron = createServerFn({ method: "POST" })
{ new: true },
);
if (!profile) {
- return new Response("Profile not found", { status: 404 });
+ throw notFound({ data: "Profile not found" });
}
await ScanModel.deleteScheduledForProfile(profile);
@@ -187,7 +198,7 @@ export const updateProfileCron = createServerFn({ method: "POST" })
export const updateProfileSettings = createServerFn({ method: "POST" })
.middleware([dbMiddleware, profileAuthorizeMiddleware])
- .validator(
+ .inputValidator(
z.object({
profileId: z.string(),
cronExpression: z.string().optional(),
@@ -208,7 +219,7 @@ export const updateProfileSettings = createServerFn({ method: "POST" })
}) => {
const validationResult = validateCron(cronExpression);
if (validationResult !== true) {
- return validationResult;
+ throw validationResult;
}
const cronUpdateSet = cronExpression
@@ -229,7 +240,7 @@ export const updateProfileSettings = createServerFn({ method: "POST" })
{ new: true },
);
if (!profile) {
- return new Response("Profile not found", { status: 404 });
+ throw notFound({ data: "Profile not found" });
}
return profile.toJSON() as unknown as ScanProfile;
},
@@ -237,7 +248,7 @@ export const updateProfileSettings = createServerFn({ method: "POST" })
export const deleteProfile = createServerFn({ method: "POST" })
.middleware([profileIdAuthorizeMiddleware])
- .validator(z.string())
+ .inputValidator(z.string())
.handler(async ({ data: profileId }) => {
await ScanProfileModel.delete(profileId);
});
diff --git a/packages/web2/app/actions/scan.ts b/packages/web2/app/actions/scan.ts
index a58282e..bd0cedb 100644
--- a/packages/web2/app/actions/scan.ts
+++ b/packages/web2/app/actions/scan.ts
@@ -5,7 +5,7 @@ import { dbMiddleware, profileAuthorizeMiddleware } from "./middleware.js";
export const startScan = createServerFn({ method: "POST" })
.middleware([dbMiddleware, profileAuthorizeMiddleware])
- .validator(
+ .inputValidator(
z.object({ profileId: z.string(), isSystemScan: z.boolean().optional() }),
)
.handler(async ({ data: { profileId, isSystemScan } }) => {
diff --git a/packages/web2/app/api.ts b/packages/web2/app/api.ts
deleted file mode 100644
index b8cc194..0000000
--- a/packages/web2/app/api.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-// app/api.ts
-import {
- createStartAPIHandler,
- defaultAPIFileRouteHandler,
-} from "@tanstack/react-start/api";
-import { dbConnect } from "extension-a11y-checker-storage";
-
-await dbConnect();
-
-export default createStartAPIHandler(defaultAPIFileRouteHandler);
diff --git a/packages/web2/app/api/helpers.ts b/packages/web2/app/api/helpers.ts
index 17f9e41..f8b0e73 100644
--- a/packages/web2/app/api/helpers.ts
+++ b/packages/web2/app/api/helpers.ts
@@ -1,7 +1,12 @@
-import { logger } from "../logger.js";
+import { logger } from "~/logger.js";
import { isNotFound } from "@tanstack/react-router";
import { json } from "@tanstack/react-start";
-import { SafeParseReturnType, SafeParseSuccess, ZodType } from "zod";
+import {
+ treeifyError,
+ ZodSafeParseResult,
+ ZodSafeParseSuccess,
+ ZodType,
+} from "zod";
export const handleAPIError = (e: unknown, action?: string) => {
if (e instanceof Response) {
@@ -15,22 +20,20 @@ export const handleAPIError = (e: unknown, action?: string) => {
return json({ message: "Internal Server Error", action }, { status: 500 });
};
-export const assertValidationSuccess: (
- parseResult: SafeParseReturnType,
-) => asserts parseResult is SafeParseSuccess = (
- parseResult: SafeParseReturnType,
-): asserts parseResult is SafeParseSuccess => {
+export function assertValidationSuccess(
+ parseResult: ZodSafeParseResult,
+): asserts parseResult is ZodSafeParseSuccess {
if (!parseResult.success) {
logger.debug(parseResult.error);
throw json(
{
message: "Input validation failed",
- error: { ...parseResult.error, name: "ValidationError" },
+ error: { ...treeifyError(parseResult.error), name: "ValidationError" },
},
{ status: 400 },
);
}
-};
+}
/*
* Due to a missing typescript feature, the return type of this function can not be
@@ -42,8 +45,8 @@ export const assertValidationSuccess: (
* // ^ this will ensure `parsedInput` gets the type information
* ```
*/
-export const assertValidation = async (
- schema: ZodType,
+export const assertValidation = async (
+ schema: T,
value: unknown,
) => {
const validationResult = await schema.safeParseAsync(value);
diff --git a/packages/web2/app/client.tsx b/packages/web2/app/client.tsx
deleted file mode 100644
index d5a3e71..0000000
--- a/packages/web2/app/client.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-// app/client.tsx
-///
-import { hydrateRoot } from "react-dom/client";
-import { StartClient } from "@tanstack/react-start";
-import { createRouter } from "./router";
-
-const router = createRouter();
-
-hydrateRoot(document, );
diff --git a/packages/web2/app/components/create/components/DomainSelect.tsx b/packages/web2/app/components/create/components/DomainSelect.tsx
index d96ba11..1eb0e54 100644
--- a/packages/web2/app/components/create/components/DomainSelect.tsx
+++ b/packages/web2/app/components/create/components/DomainSelect.tsx
@@ -8,7 +8,7 @@ import {
Text,
} from "@mittwald/flow-remote-react-components";
import { useServerFn } from "@tanstack/react-start";
-import { getDomains as getDomainsServerFn } from "../../../actions/domain.js";
+import { getDomains as getDomainsServerFn } from "~/actions/domain.js";
import { useQuery } from "@tanstack/react-query";
import { Field } from "@mittwald/flow-remote-react-components/react-hook-form";
diff --git a/packages/web2/app/components/create/components/domain.tsx b/packages/web2/app/components/create/components/domain.tsx
index e62d1b5..78d5db2 100644
--- a/packages/web2/app/components/create/components/domain.tsx
+++ b/packages/web2/app/components/create/components/domain.tsx
@@ -5,7 +5,7 @@ import {
TextField,
} from "@mittwald/flow-remote-react-components";
import { Field } from "@mittwald/flow-remote-react-components/react-hook-form";
-import { extractDomainFromUrl } from "../helpers.ts";
+import { extractDomainFromUrl } from "~/components/create/helpers.ts";
import { UseFormReturn } from "react-hook-form";
interface DomainFormValues {
diff --git a/packages/web2/app/components/create/components/pathsList.tsx b/packages/web2/app/components/create/components/pathsList.tsx
index 4a00090..03f945e 100644
--- a/packages/web2/app/components/create/components/pathsList.tsx
+++ b/packages/web2/app/components/create/components/pathsList.tsx
@@ -1,4 +1,4 @@
-import { UseFormReturn } from "react-hook-form";
+import { UseFormReturn, useWatch } from "react-hook-form";
import { useState } from "react";
import {
Align,
@@ -12,8 +12,11 @@ import {
TextField,
typedList,
} from "@mittwald/flow-remote-react-components";
-import { FormValues } from "../types.ts";
-import { extractPathFromUrl, prependPathWithSlash } from "../helpers.ts";
+import { FormValues } from "~/components/create/types.ts";
+import {
+ extractPathFromUrl,
+ prependPathWithSlash,
+} from "~/components/create/helpers.ts";
type PathFormValues = Pick;
@@ -29,6 +32,11 @@ export const PathsList = ({
const paths = form.watch("paths");
+ useWatch({
+ control: form.control,
+ name: "paths",
+ });
+
const isValidInputValue = () => {
if (!pathInputValue.startsWith("/")) {
return (
@@ -37,7 +45,7 @@ export const PathsList = ({
);
}
- if (paths.has(pathInputValue)) {
+ if (paths.includes(pathInputValue)) {
return "Pfad ist bereits hinzugefügt.";
}
return true;
@@ -49,15 +57,17 @@ export const PathsList = ({
}
const values = form.getValues("paths");
- values.add(value);
+ values.push(value);
form.setValue("paths", values);
setTouched(false);
}
const removePathFromFormValues = (value: string) => {
const values = form.getValues("paths");
- values.delete(value.toString());
- form.setValue("paths", values);
+ form.setValue(
+ "paths",
+ values.filter((v) => v != value),
+ );
};
const PathList = typedList();
diff --git a/packages/web2/app/components/create/createModal.tsx b/packages/web2/app/components/create/createModal.tsx
index ac94359..d8252bb 100644
--- a/packages/web2/app/components/create/createModal.tsx
+++ b/packages/web2/app/components/create/createModal.tsx
@@ -12,14 +12,14 @@ import {
SegmentedControl,
Text,
} from "@mittwald/flow-remote-react-components";
-import { useForm } from "react-hook-form";
+import { useForm, UseFormReturn } from "react-hook-form";
import { Form } from "@mittwald/flow-remote-react-components/react-hook-form";
import { FormValues } from "./types.ts";
import { PathsList } from "./components/pathsList.tsx";
import { Domain } from "./components/domain.tsx";
-import { createProfile } from "../../actions/profile.ts";
-import { Route } from "../../routes/index.js";
-import { useGoToProfile } from "../../hooks/useGoTo.js";
+import { createProfile } from "~/actions/profile.ts";
+import { Route } from "~/routes";
+import { useGoToProfile } from "~/hooks/useGoTo.js";
import { DomainSelect } from "./components/DomainSelect.js";
import { useState } from "react";
@@ -31,7 +31,7 @@ export const CreateModal = () => {
const form = useForm({
defaultValues: {
domain: "",
- paths: new Set(["/"]),
+ paths: ["/"],
},
});
@@ -64,14 +64,20 @@ export const CreateModal = () => {
setShowCustomDomain(!showCustomDomain)}
+ onChange={() => setShowCustomDomain((value) => !value)}
>
mStudio Domain
Individuelle Eingabe
{!showCustomDomain && }
- {showCustomDomain && }
+ {showCustomDomain && (
+ >
+ }
+ />
+ )}
Unterseiten hinzufügen
@@ -79,7 +85,10 @@ export const CreateModal = () => {
deiner Website im Blick zu behalten.
-
+ >}
+ autoFocus={!!form.getValues("domain")}
+ />
diff --git a/packages/web2/app/components/create/types.ts b/packages/web2/app/components/create/types.ts
index fd5885b..56103d6 100644
--- a/packages/web2/app/components/create/types.ts
+++ b/packages/web2/app/components/create/types.ts
@@ -1,4 +1,4 @@
export interface FormValues {
domain: string;
- paths: Set;
+ paths: string[];
}
diff --git a/packages/web2/app/components/errorRoot.tsx b/packages/web2/app/components/errorRoot.tsx
index ec61b86..58ccc72 100644
--- a/packages/web2/app/components/errorRoot.tsx
+++ b/packages/web2/app/components/errorRoot.tsx
@@ -1,5 +1,5 @@
import { ErrorRouteComponent } from "@tanstack/react-router";
-import { RootDocument } from "./rootDocument.tsx";
+import { RootDocument } from "./rootDocument";
export const ErrorRoot: ErrorRouteComponent = ({ error, info }) => {
return (
diff --git a/packages/web2/app/components/list/feedbackBox.tsx b/packages/web2/app/components/list/feedbackBox.tsx
index 37230ae..190ce2e 100644
--- a/packages/web2/app/components/list/feedbackBox.tsx
+++ b/packages/web2/app/components/list/feedbackBox.tsx
@@ -9,7 +9,7 @@ import {
Text,
LayoutCard,
} from "@mittwald/flow-remote-react-components";
-import martinImage from "../../feedback-person.webp?inline";
+import martinImage from "~/feedback-person.webp?inline";
export const FeedbackBox = () => {
return (
diff --git a/packages/web2/app/components/list/noProfiles.tsx b/packages/web2/app/components/list/noProfiles.tsx
index e5f10f2..9b4f594 100644
--- a/packages/web2/app/components/list/noProfiles.tsx
+++ b/packages/web2/app/components/list/noProfiles.tsx
@@ -8,7 +8,7 @@ import {
Text,
} from "@mittwald/flow-remote-react-components";
import { IconAccessible } from "@tabler/icons-react";
-import { CreateModal } from "../create/createModal.tsx";
+import { CreateModal } from "~/components/create/createModal.tsx";
export const NoProfiles = () => {
return (
diff --git a/packages/web2/app/components/list/profileListContextMenu.tsx b/packages/web2/app/components/list/profileListContextMenu.tsx
index 4e9d2bf..3885429 100644
--- a/packages/web2/app/components/list/profileListContextMenu.tsx
+++ b/packages/web2/app/components/list/profileListContextMenu.tsx
@@ -1,5 +1,5 @@
-import { ScanProfile } from "../../api/types.ts";
-import { useGoToProfile } from "../../hooks/useGoTo.tsx";
+import { ScanProfile } from "~/api/types.ts";
+import { useGoToProfile } from "~/hooks/useGoTo.tsx";
import { useRouter } from "@tanstack/react-router";
import {
ContextMenu,
@@ -10,11 +10,11 @@ import {
MenuItem,
useOverlayController,
} from "@mittwald/flow-remote-react-components";
-import { startScan } from "../../actions/scan.ts";
+import { startScan } from "~/actions/scan.ts";
import { IconWorldSearch } from "@tabler/icons-react";
-import { RenameProfileModal } from "../profile/modals/renameProfileModal.tsx";
-import { DeleteConfirmationModal } from "../profile/modals/deleteConfirmation.tsx";
-import { isRunningOrPending } from "../profile/helpers.ts";
+import { RenameProfileModal } from "~/components/profile/modals/renameProfileModal.tsx";
+import { DeleteConfirmationModal } from "~/components/profile/modals/deleteConfirmation.tsx";
+import { isRunningOrPending } from "~/components/profile/helpers.ts";
export function ProfileListContextMenu({ profile }: { profile: ScanProfile }) {
const goToProfile = useGoToProfile();
diff --git a/packages/web2/app/components/list/profileListItemView.tsx b/packages/web2/app/components/list/profileListItemView.tsx
index 1c33bf8..586312e 100644
--- a/packages/web2/app/components/list/profileListItemView.tsx
+++ b/packages/web2/app/components/list/profileListItemView.tsx
@@ -1,5 +1,5 @@
-import { ScanProfile } from "../../api/types.ts";
-import { isPending, isRunning } from "../profile/helpers.ts";
+import { ScanProfile } from "~/api/types.ts";
+import { isPending, isRunning } from "~/components/profile/helpers.ts";
import {
AlertBadge,
Avatar,
diff --git a/packages/web2/app/components/list/profilesList.tsx b/packages/web2/app/components/list/profilesList.tsx
index 3d94b19..8186577 100644
--- a/packages/web2/app/components/list/profilesList.tsx
+++ b/packages/web2/app/components/list/profilesList.tsx
@@ -1,14 +1,14 @@
-import { ScanProfile } from "../../api/types.ts";
+import { ScanProfile } from "~/api/types.ts";
import {
ActionGroup,
Flex,
Text,
typedList,
} from "@mittwald/flow-remote-react-components";
-import { isRunningOrPending } from "../profile/helpers.ts";
-import { useAutoRefresh } from "../../hooks/useAutoRefresh.tsx";
-import { useGoToProfile } from "../../hooks/useGoTo.tsx";
-import { CreateProfileButton } from "../create/createProfileButton.tsx";
+import { isRunningOrPending } from "~/components/profile/helpers.ts";
+import { useAutoRefresh } from "~/hooks/useAutoRefresh.tsx";
+import { useGoToProfile } from "~/hooks/useGoTo.tsx";
+import { CreateProfileButton } from "~/components/create/createProfileButton.tsx";
import { ProfileListItemView } from "./profileListItemView.tsx";
export const ProfilesList = ({ profiles }: { profiles: ScanProfile[] }) => {
diff --git a/packages/web2/app/components/notFound.tsx b/packages/web2/app/components/notFound.tsx
new file mode 100644
index 0000000..db117f2
--- /dev/null
+++ b/packages/web2/app/components/notFound.tsx
@@ -0,0 +1,11 @@
+import { NotFoundRouteComponent } from "@tanstack/react-router";
+import { LayoutCard, Text } from "@mittwald/flow-remote-react-components";
+
+export const NotFound: NotFoundRouteComponent = ({ data }) => {
+ console.error(data);
+ return (
+
+ 404 – Nicht gefunden
+
+ );
+};
diff --git a/packages/web2/app/components/notFoundRoot.tsx b/packages/web2/app/components/notFoundRoot.tsx
index 6c8c4ef..cc91c1e 100644
--- a/packages/web2/app/components/notFoundRoot.tsx
+++ b/packages/web2/app/components/notFoundRoot.tsx
@@ -1,11 +1,11 @@
import { NotFoundRouteComponent } from "@tanstack/react-router";
-import { RootDocument } from "./rootDocument.js";
+import { RootDocument } from "./rootDocument.tsx";
+import { NotFound } from "./notFound.tsx";
-export const NotFoundRoot: NotFoundRouteComponent = ({ data }) => {
- console.error(data);
+export const NotFoundRoot: NotFoundRouteComponent = (props) => {
return (
- 404 – Nicht gefunden
+
);
};
diff --git a/packages/web2/app/components/profile/CronFields/lib.ts b/packages/web2/app/components/profile/CronFields/lib.ts
index c9e8990..24f4de2 100644
--- a/packages/web2/app/components/profile/CronFields/lib.ts
+++ b/packages/web2/app/components/profile/CronFields/lib.ts
@@ -1,7 +1,7 @@
import { Time } from "@internationalized/date";
import cronstrue from "cronstrue";
import "cronstrue/locales/de";
-import parser from "cron-parser";
+import { CronExpressionParser } from "cron-parser";
// todo: get actual language
export const getCronText = (cronSyntax: string) => {
@@ -154,7 +154,7 @@ export const isSmallIntervall = (schedule: string) => {
return false;
}
try {
- const interval = parser.parseExpression(schedule);
+ const interval = CronExpressionParser.parse(schedule);
const firstDate = interval.next();
const secondDate = interval.next();
const hoursDiff =
@@ -170,7 +170,7 @@ export const isSmallIntervall = (schedule: string) => {
export const getExecutions = (cron: string) => {
try {
- const interval = parser.parseExpression(cron);
+ const interval = CronExpressionParser.parse(cron);
const executions: Date[] = [];
diff --git a/packages/web2/app/components/profile/currentScan.tsx b/packages/web2/app/components/profile/currentScan.tsx
index bc28fcf..98d6943 100644
--- a/packages/web2/app/components/profile/currentScan.tsx
+++ b/packages/web2/app/components/profile/currentScan.tsx
@@ -1,11 +1,11 @@
-import { Route } from "../../routes/profiles.$profileId.tsx";
+import { Route } from "~/routes/profiles.$profileId.tsx";
import {
Alert,
Align,
LoadingSpinner,
Text,
} from "@mittwald/flow-remote-react-components";
-import { isPending, isRunning } from "./helpers.ts";
+import { isPending, isRunning } from "./helpers";
const RunningScan = () => {
return (
@@ -32,7 +32,7 @@ const PendingScan = () => {
export const CurrentScan = () => {
const {
profile: { nextScan },
- } = Route.useLoaderData();
+ } = Route.useLoaderData()!;
if (!nextScan) {
return null;
diff --git a/packages/web2/app/components/profile/errorScan.tsx b/packages/web2/app/components/profile/errorScan.tsx
index cdf9fc0..7dfe2c5 100644
--- a/packages/web2/app/components/profile/errorScan.tsx
+++ b/packages/web2/app/components/profile/errorScan.tsx
@@ -1,8 +1,8 @@
import { FC, ReactNode } from "react";
-import { Scan, ScanProfile } from "../../api/types.js";
+import { Scan, ScanProfile } from "~/api/types.js";
import { DefaultErrorView } from "./errorScans/defaultErrorView.js";
import { ErrorViewWithEditDomain } from "./errorScans/ErrorViewWithEditDomain.js";
-import { ErrorViewWithoutEditDomain } from "./errorScans/ErrorViewWithoutEditDomain.js";
+import { ErrorViewWithoutEditDomain } from "./errorScans/ErrorViewWithoutEditDomain";
interface ErrorTexts {
headline: string;
diff --git a/packages/web2/app/components/profile/errorScans/ErrorViewWithEditDomain.tsx b/packages/web2/app/components/profile/errorScans/ErrorViewWithEditDomain.tsx
index efde1e5..03b0190 100644
--- a/packages/web2/app/components/profile/errorScans/ErrorViewWithEditDomain.tsx
+++ b/packages/web2/app/components/profile/errorScans/ErrorViewWithEditDomain.tsx
@@ -11,7 +11,7 @@ import {
useOverlayController,
} from "@mittwald/flow-remote-react-components";
import { RestartScanButton } from "./restartScanButton.js";
-import { ChangeDomainModal } from "../modals/changeDomainModal.js";
+import { ChangeDomainModal } from "~/components/profile/modals/changeDomainModal.js";
export const ErrorViewWithEditDomain: FC = ({
profile,
diff --git a/packages/web2/app/components/profile/errorScans/restartScanButton.tsx b/packages/web2/app/components/profile/errorScans/restartScanButton.tsx
index 38dbf7e..fccff71 100644
--- a/packages/web2/app/components/profile/errorScans/restartScanButton.tsx
+++ b/packages/web2/app/components/profile/errorScans/restartScanButton.tsx
@@ -2,7 +2,7 @@ import { FC } from "react";
import { BaseProps } from "./types.js";
import { useRouter } from "@tanstack/react-router";
import { Action, Button } from "@mittwald/flow-remote-react-components";
-import { startScan } from "../../../actions/scan.js";
+import { startScan } from "~/actions/scan";
export const RestartScanButton: FC = ({ profile, scanId }) => {
const router = useRouter();
diff --git a/packages/web2/app/components/profile/errorScans/types.ts b/packages/web2/app/components/profile/errorScans/types.ts
index b153484..930a10e 100644
--- a/packages/web2/app/components/profile/errorScans/types.ts
+++ b/packages/web2/app/components/profile/errorScans/types.ts
@@ -1,5 +1,5 @@
import { ReactNode } from "react";
-import { ScanProfile } from "../../../api/types.js";
+import { ScanProfile } from "~/api/types.js";
export interface BaseProps {
profile: ScanProfile;
diff --git a/packages/web2/app/components/profile/helpers.ts b/packages/web2/app/components/profile/helpers.ts
index 27b7128..e9dff4b 100644
--- a/packages/web2/app/components/profile/helpers.ts
+++ b/packages/web2/app/components/profile/helpers.ts
@@ -1,4 +1,4 @@
-import { Scan } from "../../api/types.ts";
+import { Scan } from "~/api/types";
export const isRunning = (scan: Scan) => {
return scan.status === "running";
diff --git a/packages/web2/app/components/profile/modals/EditIntervalModal.tsx b/packages/web2/app/components/profile/modals/EditIntervalModal.tsx
index 608054d..8a23ae6 100644
--- a/packages/web2/app/components/profile/modals/EditIntervalModal.tsx
+++ b/packages/web2/app/components/profile/modals/EditIntervalModal.tsx
@@ -16,10 +16,10 @@ import { Time } from "@internationalized/date";
import {
CronInterval,
getIntervalValueFromCronSyntax,
-} from "../CronFields/lib.js";
-import { ScanProfile } from "../../../api/types.js";
-import { CronFields } from "../CronFields/CronFields.js";
-import { updateProfileCron } from "../../../actions/profile.js";
+} from "~/components/profile/CronFields/lib.js";
+import { ScanProfile } from "~/api/types";
+import { CronFields } from "~/components/profile/CronFields/CronFields.js";
+import { updateProfileCron } from "~/actions/profile";
import { useRouter } from "@tanstack/react-router";
interface Props {
diff --git a/packages/web2/app/components/profile/modals/changeDomainModal.tsx b/packages/web2/app/components/profile/modals/changeDomainModal.tsx
index 00be052..0eb7bef 100644
--- a/packages/web2/app/components/profile/modals/changeDomainModal.tsx
+++ b/packages/web2/app/components/profile/modals/changeDomainModal.tsx
@@ -1,4 +1,4 @@
-import { ScanProfile } from "../../../api/types.ts";
+import { ScanProfile } from "~/api/types";
import { useForm } from "react-hook-form";
import {
Action,
@@ -11,10 +11,10 @@ import {
Section,
} from "@mittwald/flow-remote-react-components";
import { Form } from "@mittwald/flow-remote-react-components/react-hook-form";
-import { updateProfileDomain } from "../../../actions/profile.ts";
+import { updateProfileDomain } from "~/actions/profile";
import { useRouter } from "@tanstack/react-router";
-import { startScan } from "../../../actions/scan.js";
-import { Domain } from "../../create/components/domain.js";
+import { startScan } from "~/actions/scan";
+import { Domain } from "~/components/create/components/domain.js";
interface FormValues {
domain: string;
diff --git a/packages/web2/app/components/profile/modals/deleteConfirmation.tsx b/packages/web2/app/components/profile/modals/deleteConfirmation.tsx
index 57b1601..f21083c 100644
--- a/packages/web2/app/components/profile/modals/deleteConfirmation.tsx
+++ b/packages/web2/app/components/profile/modals/deleteConfirmation.tsx
@@ -9,8 +9,8 @@ import {
Section,
Text,
} from "@mittwald/flow-remote-react-components";
-import { ScanProfile } from "../../../api/types.ts";
-import { deleteProfile } from "../../../actions/profile.ts";
+import { ScanProfile } from "~/api/types";
+import { deleteProfile } from "~/actions/profile";
export const DeleteConfirmationModal = ({
profile,
diff --git a/packages/web2/app/components/profile/modals/editGenerals.tsx b/packages/web2/app/components/profile/modals/editGenerals.tsx
index 39d7035..5fa6637 100644
--- a/packages/web2/app/components/profile/modals/editGenerals.tsx
+++ b/packages/web2/app/components/profile/modals/editGenerals.tsx
@@ -1,4 +1,4 @@
-import { ScanProfile } from "../../../api/types.ts";
+import { ScanProfile } from "~/api/types";
import {
Action,
ActionGroup,
@@ -21,9 +21,9 @@ import {
typedField,
} from "@mittwald/flow-remote-react-components/react-hook-form";
import { useRouter } from "@tanstack/react-router";
-import { updateProfileSettings } from "../../../actions/profile.ts";
-import { WcagStandardContextualHelp } from "../wcagStandardContextualHelp.tsx";
-import { CriteriaContextualHelp } from "../criteriaContextualHelp.tsx";
+import { updateProfileSettings } from "~/actions/profile";
+import { WcagStandardContextualHelp } from "~/components/profile/wcagStandardContextualHelp";
+import { CriteriaContextualHelp } from "~/components/profile/criteriaContextualHelp";
interface FormValues {
cronExpression?: string;
@@ -75,6 +75,7 @@ export const EditGeneralsModal = ({ profile }: { profile: ScanProfile }) => {