Unraid deployment fixes and generator improvements
- Add Next.js rewrites to proxy API calls through same origin (fixes login/media on werkout.treytartt.com) - Fix mediaUrl() in DayCard and ExerciseRow to use relative paths in production - Add proxyTimeout for long-running workout generation endpoints - Add CSRF trusted origin for treytartt.com - Split docker-compose into production (Unraid) and dev configs - Show display_name and descriptions on workout type cards - Generator: rules engine improvements, movement enforcement, exercise selector updates - Add new test files for rules drift, workout research generation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -84,14 +84,14 @@ export function WorkoutTypesStep({
|
||||
isSelected ? "text-accent" : "text-zinc-100"
|
||||
}`}
|
||||
>
|
||||
{wt.name}
|
||||
{wt.display_name || wt.name}
|
||||
</span>
|
||||
<Badge variant={intensityVariant[wt.typical_intensity] || "default"}>
|
||||
{wt.typical_intensity}
|
||||
</Badge>
|
||||
</div>
|
||||
{wt.description && (
|
||||
<p className="text-sm text-zinc-400 line-clamp-2">
|
||||
<p className="text-sm text-zinc-400">
|
||||
{wt.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -63,7 +63,10 @@ function XIcon({ className = "" }: { className?: string }) {
|
||||
|
||||
function mediaUrl(path: string): string {
|
||||
if (typeof window === "undefined") return path;
|
||||
return `${window.location.protocol}//${window.location.hostname}:8001${path}`;
|
||||
if (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1") {
|
||||
return `${window.location.protocol}//${window.location.hostname}:8001${path}`;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
function PlayIcon({ className = "" }: { className?: string }) {
|
||||
@@ -301,6 +304,7 @@ export function DayCard({
|
||||
focus_area: previewDay.focus_area,
|
||||
workout_type_id: previewDay.workout_type_id,
|
||||
date: previewDay.date,
|
||||
plan_id: previewDay.plan_id,
|
||||
});
|
||||
onPreviewDayChange(previewDayIndex, newDay);
|
||||
} catch (err) {
|
||||
@@ -421,6 +425,17 @@ export function DayCard({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{previewDay.warnings && previewDay.warnings.length > 0 && (
|
||||
<div className="rounded-lg border border-yellow-500/30 bg-yellow-500/10 p-2 text-xs text-yellow-200">
|
||||
<p className="font-semibold mb-1">Warnings</p>
|
||||
<ul className="list-disc list-inside space-y-0.5">
|
||||
{previewDay.warnings.map((w, idx) => (
|
||||
<li key={idx}>{w}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Supersets */}
|
||||
{spec && spec.supersets.length > 0 && (
|
||||
<div className="flex flex-col gap-2">
|
||||
|
||||
@@ -4,7 +4,10 @@ import type { SupersetExercise } from "@/lib/types";
|
||||
|
||||
function mediaUrl(path: string): string {
|
||||
if (typeof window === "undefined") return path;
|
||||
return `${window.location.protocol}//${window.location.hostname}:8001${path}`;
|
||||
if (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1") {
|
||||
return `${window.location.protocol}//${window.location.hostname}:8001${path}`;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
interface ExerciseRowProps {
|
||||
|
||||
7
werkout-frontend/eslint.config.mjs
Normal file
7
werkout-frontend/eslint.config.mjs
Normal file
@@ -0,0 +1,7 @@
|
||||
import nextVitals from "eslint-config-next/core-web-vitals";
|
||||
|
||||
const config = [
|
||||
...nextVitals,
|
||||
];
|
||||
|
||||
export default config;
|
||||
@@ -1,6 +1,10 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
// v2
|
||||
const nextConfig = {
|
||||
skipTrailingSlashRedirect: true,
|
||||
experimental: {
|
||||
proxyTimeout: 120000, // 2 minutes for long-running workout generation
|
||||
},
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
@@ -16,12 +20,24 @@ const nextConfig = {
|
||||
],
|
||||
},
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
source: "/media/:path*",
|
||||
destination: "http://localhost:8000/media/:path*",
|
||||
},
|
||||
const djangoUrl = process.env.DJANGO_INTERNAL_URL || "http://localhost:8000";
|
||||
// Helper: for each Django prefix, create two rewrites:
|
||||
// 1. with trailing slash preserved
|
||||
// 2. without trailing slash → add it (Django requires trailing slashes)
|
||||
const djangoPrefixes = [
|
||||
"media", "registered_user", "exercise", "muscle",
|
||||
"equipment", "workout", "generator", "videos", "admin",
|
||||
];
|
||||
return djangoPrefixes.flatMap((prefix) => [
|
||||
{
|
||||
source: `/${prefix}/:path*/`,
|
||||
destination: `${djangoUrl}/${prefix}/:path*/`,
|
||||
},
|
||||
{
|
||||
source: `/${prefix}/:path*`,
|
||||
destination: `${djangoUrl}/${prefix}/:path*/`,
|
||||
},
|
||||
]);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
|
||||
Reference in New Issue
Block a user