diff --git a/.github/workflows/container-scan.yml b/.github/workflows/container-scan.yml
index e6b0ca5..fe44b46 100644
--- a/.github/workflows/container-scan.yml
+++ b/.github/workflows/container-scan.yml
@@ -15,7 +15,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Build an image from Dockerfile
- run: docker build -t git.dominikstahl.dev/dhbw-we/meetup:${{ github.sha }} .
+ run: docker buildx build -t meetup_trivy .
- name: Install Trivy
run: |
@@ -23,8 +23,8 @@ jobs:
- name: Run Trivy vulnerability scanner
run: |
- trivy image --exit-code 1 --severity HIGH,CRITICAL,MEDIUM --ignore-unfixed --no-progress --format table git.dominikstahl.dev/dhbw-we/meetup:${{ github.sha }}
- trivy image --exit-code 1 --severity HIGH,CRITICAL,MEDIUM --ignore-unfixed --no-progress --format json git.dominikstahl.dev/dhbw-we/meetup:${{ github.sha }} > trivy-report.json
+ trivy image --exit-code 1 --severity HIGH,CRITICAL,MEDIUM --ignore-unfixed --no-progress --format table meetup_trivy
+ trivy image --exit-code 1 --severity HIGH,CRITICAL,MEDIUM --ignore-unfixed --no-progress --format json meetup_trivy > trivy-report.json
- name: Upload Trivy report
uses: forgejo/upload-artifact@v4
@@ -33,6 +33,5 @@ jobs:
- name: Clean up Docker
run: |
- docker builder prune -af --keep-storage 2GB
- docker rmi $(docker images --filter=reference="git.dominikstahl.dev/dhbw-we/meetup:*" -q)
- docker image prune -f
+ docker buildx prune --filter=until=48h -f
+ docker image rm meetup_trivy
diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml
index 9ed6a6b..0b819e7 100644
--- a/.github/workflows/docker-build.yml
+++ b/.github/workflows/docker-build.yml
@@ -45,6 +45,7 @@ jobs:
with:
push: true
tags: git.dominikstahl.dev/${{ env.REPO }}:${{ steps.get-ref.outputs.tag}}
+ cache-from: type=registry,ref=git.dominikstahl.dev/${{ env.REPO }}:buildcache
- name: Build and push (push_tag)
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6
@@ -52,6 +53,7 @@ jobs:
with:
push: true
tags: git.dominikstahl.dev/${{ env.REPO }}:${{ steps.get-ref.outputs.tag }},git.dominikstahl.dev/${{ env.REPO }}:latest
+ cache-from: type=registry,ref=git.dominikstahl.dev/${{ env.REPO }}:buildcache
- name: Build and push (push_branch)
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6
@@ -59,8 +61,9 @@ jobs:
with:
push: true
tags: git.dominikstahl.dev/${{ env.REPO }}:${{ steps.get-ref.outputs.tag }}
+ cache-from: type=registry,ref=git.dominikstahl.dev/${{ env.REPO }}:buildcache
+ cache-to: type=registry,ref=git.dominikstahl.dev/${{ env.REPO }}:buildcache,mode=max
- name: Clean up Docker
run: |
- docker builder prune -af --keep-storage 2GB
- docker image prune -f
+ docker buildx prune --filter=until=48h -f
diff --git a/.yarnrc.yml b/.yarnrc.yml
new file mode 100644
index 0000000..3186f3f
--- /dev/null
+++ b/.yarnrc.yml
@@ -0,0 +1 @@
+nodeLinker: node-modules
diff --git a/Dockerfile b/Dockerfile
index faee65e..2711500 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,13 +4,15 @@ FROM node:22-alpine@sha256:ad1aedbcc1b0575074a91ac146d6956476c1f9985994810e4ee02
FROM base AS deps
WORKDIR /app
-COPY package.json yarn.lock ./
+RUN corepack enable
+COPY package.json yarn.lock .yarnrc.yml ./
RUN yarn install --frozen-lockfile
# ----- Build -----
FROM base AS builder
WORKDIR /app
+RUN corepack enable
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN yarn build
diff --git a/next.config.ts b/next.config.ts
index 94647ad..164b423 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -2,6 +2,16 @@ import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
output: 'standalone',
+ images: {
+ remotePatterns: [
+ {
+ protocol: 'https',
+ hostname: 'img1.wikia.nocookie.net',
+ port: '',
+ pathname: '/**',
+ },
+ ],
+ },
};
export default nextConfig;
diff --git a/package.json b/package.json
index 552ac7b..b784d68 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,8 @@
"@fortawesome/free-regular-svg-icons": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
+ "@radix-ui/react-dropdown-menu": "^2.1.14",
+ "@radix-ui/react-hover-card": "^1.1.13",
"@radix-ui/react-label": "^2.1.6",
"@radix-ui/react-scroll-area": "^1.2.8",
"@radix-ui/react-select": "^2.2.4",
@@ -48,5 +50,5 @@
"tw-animate-css": "1.2.9",
"typescript": "5.8.3"
},
- "packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610"
+ "packageManager": "yarn@4.9.1"
}
diff --git a/src/app/home/page.tsx b/src/app/home/page.tsx
index c85d75b..c61abdd 100644
--- a/src/app/home/page.tsx
+++ b/src/app/home/page.tsx
@@ -1,12 +1,16 @@
import { Logout } from '@/components/user/sso-logout-button';
import { RedirectButton } from '@/components/user/redirect-button';
+import { ThemePicker } from '@/components/user/theme-picker';
export default function Home() {
return (
-
-
Home
-
-
+
);
}
diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx
index b843b45..714e996 100644
--- a/src/app/login/page.tsx
+++ b/src/app/login/page.tsx
@@ -2,9 +2,17 @@ import { auth } from '@/auth';
import SSOLogin from '@/components/user/sso-login-button';
import LoginForm from '@/components/user/login-form';
import { redirect } from 'next/navigation';
+import { Button } from '@/components/ui/button';
+import Image from 'next/image';
import '@/app/globals.css';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
+import { ThemePicker } from '@/components/user/theme-picker';
+import {
+ HoverCard,
+ HoverCardTrigger,
+ HoverCardContent,
+} from '@/components/ui/hover-card';
export default async function LoginPage() {
const session = await auth();
@@ -15,20 +23,40 @@ export default async function LoginPage() {
return (
-
-
- Login
-
-
-
+
+
+
+
+
+
+
+ Login
+
+
+
-
+
- {process.env.AUTH_AUTHENTIK_ISSUER && (
-
- )}
-
-
+ {process.env.AUTH_AUTHENTIK_ISSUER && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/src/components/labeled-input.tsx b/src/components/labeled-input.tsx
index af8879e..7b4768a 100644
--- a/src/components/labeled-input.tsx
+++ b/src/components/labeled-input.tsx
@@ -1,4 +1,5 @@
import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
export default function LabeledInput({
type,
@@ -11,17 +12,17 @@ export default function LabeledInput({
placeholder?: string;
value?: string;
}) {
- const randomId = Math.random().toString(36).substring(2, 15);
+ const elementId = Math.random().toString(36).substring(2, 15);
return (
-
-
+
+
);
diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx
new file mode 100644
index 0000000..7a8804e
--- /dev/null
+++ b/src/components/ui/dropdown-menu.tsx
@@ -0,0 +1,257 @@
+'use client';
+
+import * as React from 'react';
+import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
+import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react';
+
+import { cn } from '@/lib/utils';
+
+function DropdownMenu({
+ ...props
+}: React.ComponentProps
) {
+ return ;
+}
+
+function DropdownMenuPortal({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DropdownMenuTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DropdownMenuContent({
+ className,
+ sideOffset = 4,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
+
+function DropdownMenuGroup({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DropdownMenuItem({
+ className,
+ inset,
+ variant = 'default',
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean;
+ variant?: 'default' | 'destructive';
+}) {
+ return (
+
+ );
+}
+
+function DropdownMenuCheckboxItem({
+ className,
+ children,
+ checked,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ );
+}
+
+function DropdownMenuRadioGroup({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DropdownMenuRadioItem({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ );
+}
+
+function DropdownMenuLabel({
+ className,
+ inset,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean;
+}) {
+ return (
+
+ );
+}
+
+function DropdownMenuSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DropdownMenuShortcut({
+ className,
+ ...props
+}: React.ComponentProps<'span'>) {
+ return (
+
+ );
+}
+
+function DropdownMenuSub({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function DropdownMenuSubTrigger({
+ className,
+ inset,
+ children,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean;
+}) {
+ return (
+
+ {children}
+
+
+ );
+}
+
+function DropdownMenuSubContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export {
+ DropdownMenu,
+ DropdownMenuPortal,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuLabel,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioGroup,
+ DropdownMenuRadioItem,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuSub,
+ DropdownMenuSubTrigger,
+ DropdownMenuSubContent,
+};
diff --git a/src/components/ui/hover-card.tsx b/src/components/ui/hover-card.tsx
new file mode 100644
index 0000000..5c120a9
--- /dev/null
+++ b/src/components/ui/hover-card.tsx
@@ -0,0 +1,44 @@
+'use client';
+
+import * as React from 'react';
+import * as HoverCardPrimitive from '@radix-ui/react-hover-card';
+
+import { cn } from '@/lib/utils';
+
+function HoverCard({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function HoverCardTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function HoverCardContent({
+ className,
+ align = 'center',
+ sideOffset = 4,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
+
+export { HoverCard, HoverCardTrigger, HoverCardContent };
diff --git a/src/components/user/login-form.tsx b/src/components/user/login-form.tsx
index 47e1945..20438e8 100644
--- a/src/components/user/login-form.tsx
+++ b/src/components/user/login-form.tsx
@@ -3,7 +3,7 @@ import { Button } from '@/components/ui/button';
export default function LoginForm() {
return (
-