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 (