diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
deleted file mode 100644
index aec3c2a..0000000
--- a/.github/workflows/dependency-review.yml
+++ /dev/null
@@ -1,32 +0,0 @@
-name: Dependency Review
-
-# Runs on every Pull Request and scans newly added/updated npm packages
-# against the GitHub Advisory Database (known CVEs).
-# Fails the PR if a vulnerable or malicious package is introduced.
-
-on:
- pull_request:
- branches: [ "main" ]
-
-permissions:
- contents: read
- pull-requests: write # needed to post a summary comment on the PR
-
-jobs:
- dependency-review:
- name: Scan dependencies for vulnerabilities
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v6
-
- - name: Dependency Review
- uses: actions/dependency-review-action@v5
- with:
- # Fail on any severity: critical, high, moderate, low
- fail-on-severity: moderate
- # Post a comment on the PR with a summary of findings
- comment-summary-in-pr: always
- # Deny packages with known malware
- deny-licenses: GPL-2.0, GPL-3.0
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 3c65497..7324198 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,4 +1,4 @@
-name: 🎫 Auto Release – MSK-Scripts/msk-shop
+name: 🎫 Auto Release
on:
push:
diff --git a/app/(app)/integrations/discord/page.tsx b/app/(app)/integrations/discord/page.tsx
index a9657c8..7e02d40 100644
--- a/app/(app)/integrations/discord/page.tsx
+++ b/app/(app)/integrations/discord/page.tsx
@@ -212,41 +212,6 @@ export default async function DiscordIntegrationPage({
);
}
-function Stat({
- label,
- value,
- accent,
-}: {
- label: string;
- value: number;
- accent?: boolean;
-}) {
- return (
-
-
- {label}
-
-
- {value}
-
-
- );
-}
-
function ServerList({ servers }: { servers: GuildRow[] }) {
const active = servers.filter((s) => s.botPresent);
const inactive = servers.filter((s) => !s.botPresent);
diff --git a/app/order/[id]/page.tsx b/app/order/[id]/page.tsx
index 1c8a434..8cec71c 100644
--- a/app/order/[id]/page.tsx
+++ b/app/order/[id]/page.tsx
@@ -107,7 +107,7 @@ export default async function OrderCheckoutPage({ params }: Props) {
// PaymentIntent — wenn schon vorhanden, müssten wir ihn retrieven; für MVP
// legen wir bei jedem Page-Load ggf. neu an (idempotent über order_id metadata).
let clientSecret: string;
- let stripeAccountId: string = guildRow.stripe_account_id as string;
+ const stripeAccountId: string = guildRow.stripe_account_id as string;
try {
const pi = await createConnectPaymentIntent({
amountCents: order.amount_cents as number,
diff --git a/bot/src/events/channelMode.ts b/bot/src/events/channelMode.ts
index 6ef0ede..dc2ac19 100644
--- a/bot/src/events/channelMode.ts
+++ b/bot/src/events/channelMode.ts
@@ -3,7 +3,6 @@ import { getChannelMode } from '../db/channelModes.js';
const IMAGE_EXT = /\.(png|jpe?g|gif|webp|bmp|svg)$/i;
const VIDEO_EXT = /\.(mp4|mov|webm|mkv|avi|m4v)$/i;
-const URL_REGEX = /\bhttps?:\/\/\S+\.\S+\b/i;
function hasImageAttachment(message: Message, allowVideos: boolean): boolean {
for (const a of message.attachments.values()) {
@@ -20,14 +19,6 @@ function hasImageAttachment(message: Message, allowVideos: boolean): boolean {
return false;
}
-function hasNonImageContent(message: Message): boolean {
- const text = message.content?.trim() ?? '';
- if (text.length === 0) return false;
- // Reiner Bild-URL als Text-Inhalt ist ok.
- if (URL_REGEX.test(text) && IMAGE_EXT.test(text)) return false;
- return true;
-}
-
export function registerChannelMode(client: Client): void {
client.on(Events.MessageCreate, async (message: Message) => {
try {
diff --git a/components/AutoModForm.tsx b/components/AutoModForm.tsx
index 8c1c833..3a10c39 100644
--- a/components/AutoModForm.tsx
+++ b/components/AutoModForm.tsx
@@ -79,7 +79,6 @@ export function AutoModForm({ guildId, initial }: Props) {
>
@@ -115,7 +114,6 @@ export function AutoModForm({ guildId, initial }: Props) {
@@ -147,7 +145,6 @@ export function AutoModForm({ guildId, initial }: Props) {
@@ -179,7 +176,6 @@ export function AutoModForm({ guildId, initial }: Props) {
0}
summary={
bannedWordsCount > 0
@@ -218,13 +214,11 @@ export function AutoModForm({ guildId, initial }: Props) {
function FilterCard({
title,
- icon,
active,
summary,
children,
}: {
title: string;
- icon: string;
active: boolean;
summary: string;
children: React.ReactNode;
diff --git a/components/EmbedCreatorForm.tsx b/components/EmbedCreatorForm.tsx
index fb578df..de1e4b1 100644
--- a/components/EmbedCreatorForm.tsx
+++ b/components/EmbedCreatorForm.tsx
@@ -820,14 +820,6 @@ function renderInlineMarkdown(text: string): string {
// ============== Components-Editor (Link-Buttons in ActionRows) ==============
-const BUTTON_STYLE_CLASSES: Record, string> = {
- primary: 'bg-[#5865F2] text-white',
- secondary: 'bg-[#4E5058] text-white',
- success: 'bg-[#248046] text-white',
- danger: 'bg-[#DA373C] text-white',
- link: 'bg-[#4E5058] text-white',
-};
-
const ACTION_STYLE_CLASSES: Record, string> = {
primary: 'bg-[#5865F2] text-white',
secondary: 'bg-[#4E5058] text-white',
diff --git a/components/ModuleOverview.tsx b/components/ModuleOverview.tsx
index b7bc0d5..ff486ad 100644
--- a/components/ModuleOverview.tsx
+++ b/components/ModuleOverview.tsx
@@ -203,7 +203,7 @@ export function ModuleOverview({ guildId, modules, premium = false }: Props) {
{filtered.length === 0 ? (
- Kein Modul gefunden für „{query}".
+ Kein Modul gefunden für „{query}".
) : (
diff --git a/components/Phase2FinishForms.tsx b/components/Phase2FinishForms.tsx
index 032d45b..bcf1908 100644
--- a/components/Phase2FinishForms.tsx
+++ b/components/Phase2FinishForms.tsx
@@ -379,18 +379,24 @@ export function InviteTrackerForm({
}) {
const [enabled, setEnabled] = useState(initialEnabled);
const [rows, setRows] = useState
(null);
- const [loading, setLoading] = useState(false);
- const [pending, startTransition] = useTransition();
+ const [fetched, setFetched] = useState(false);
+ const [, startTransition] = useTransition();
+
+ // loading wird abgeleitet — kein synchrones setState im Effect.
+ const loading = enabled && !fetched;
useEffect(() => {
- if (!enabled || rows !== null) return;
- setLoading(true);
- listInviteLeaderboard(guildId)
- .then((r) => {
- if (r.ok && r.rows) setRows(r.rows);
- })
- .finally(() => setLoading(false));
- }, [enabled, rows, guildId]);
+ if (!enabled || fetched) return;
+ let active = true;
+ listInviteLeaderboard(guildId).then((r) => {
+ if (!active) return;
+ if (r.ok && r.rows) setRows(r.rows);
+ setFetched(true);
+ });
+ return () => {
+ active = false;
+ };
+ }, [enabled, fetched, guildId]);
const toggle = (next: boolean) => {
setEnabled(next);
diff --git a/components/TicketsForm.tsx b/components/TicketsForm.tsx
index 17149c9..d910cc2 100644
--- a/components/TicketsForm.tsx
+++ b/components/TicketsForm.tsx
@@ -1182,16 +1182,23 @@ function TicketListView({
status: 'open' | 'closed';
}) {
const [tickets, setTickets] = useState(null);
- const [loading, setLoading] = useState(true);
+ const [loadedKey, setLoadedKey] = useState(null);
const [viewingId, setViewingId] = useState(null);
+ const key = `${guildId}:${status}`;
+ const loading = loadedKey !== key;
+
useEffect(() => {
- setLoading(true);
+ let active = true;
listTicketsForGuild(guildId, status).then((r) => {
+ if (!active) return;
if (r.ok && r.tickets) setTickets(r.tickets);
- setLoading(false);
+ setLoadedKey(key);
});
- }, [guildId, status]);
+ return () => {
+ active = false;
+ };
+ }, [guildId, status, key]);
if (loading) {
return (
@@ -1273,14 +1280,19 @@ function TicketListView({
function FeedbackView({ guildId }: { guildId: string }) {
const [data, setData] = useState<{ feedback: TicketFeedbackRow[]; avg: number } | null>(null);
- const [loading, setLoading] = useState(true);
+ const [loadedGuild, setLoadedGuild] = useState(null);
+ const loading = loadedGuild !== guildId;
useEffect(() => {
- setLoading(true);
+ let active = true;
listTicketFeedbackForGuild(guildId).then((r) => {
+ if (!active) return;
if (r.ok) setData({ feedback: r.feedback ?? [], avg: r.avgRating ?? 0 });
- setLoading(false);
+ setLoadedGuild(guildId);
});
+ return () => {
+ active = false;
+ };
}, [guildId]);
if (loading) {
@@ -1294,7 +1306,7 @@ function FeedbackView({ guildId }: { guildId: string }) {
return (
- Noch kein Feedback. Aktiviere es in einem Panel unter „Feedback".
+ Noch kein Feedback. Aktiviere es in einem Panel unter „Feedback".
);
@@ -1352,16 +1364,21 @@ function TranscriptViewer({
ticket?: TicketSummary;
messages?: TranscriptMessageAct[];
} | null>(null);
- const [loading, setLoading] = useState(true);
+ const [loadedId, setLoadedId] = useState(null);
+ const loading = loadedId !== ticketId;
useEffect(() => {
- setLoading(true);
+ let active = true;
getTicketTranscript(guildId, ticketId).then((r) => {
+ if (!active) return;
if (r.ok) {
setData({ ticket: r.ticket, messages: r.messages });
}
- setLoading(false);
+ setLoadedId(ticketId);
});
+ return () => {
+ active = false;
+ };
}, [guildId, ticketId]);
return (