Skip to content

feat: seed learning resources and extend UI#25

Closed
markstockhausen wants to merge 46 commits into
mainfrom
feature/seed-ui-learning-resources
Closed

feat: seed learning resources and extend UI#25
markstockhausen wants to merge 46 commits into
mainfrom
feature/seed-ui-learning-resources

Conversation

@markstockhausen

Copy link
Copy Markdown
Contributor

Description

Add initial seeding data and logic for learning resources to support development/demo workflows

Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as
    expected)
  • 📝 Documentation update
  • 🎨 Style/formatting changes (no functional changes)
  • ♻️ Code refactoring (no functional changes)
  • ⚡ Performance improvements
  • 🔧 Configuration changes
  • 🧪 Test additions or modifications
  • 🚀 Deployment/DevOps changes

Related Issues

Changes Made

  • Client: N/A
  • Server: Added/updated seeding logic for learning resources.
  • Database: Added initial learning resource seed data.
  • Other: N/A

Testing

Test Cases

  • Existing functionality still works
  • New functionality works as expected
  • Edge cases handled appropriately

Manual Testing

  • Tested in development environment
  • Tested with different user roles/permissions (if applicable)

Screenshots/Videos

Before

N/A

After

N/A

vtotalova and others added 30 commits November 25, 2025 12:50
- Add unique constraint on CompetencyRelationship to prevent duplicates
- Add database indexes on CompetencyResourceLink foreign keys for performance
- Make userId optional in CompetencyResourceLink with SetNull on delete
- Add error logging and visibility in migration system
- Move database docs to root as DATABASE.md
- Sync package-lock.json with package.json (TypeScript 5.9.3)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Maximilian Anzinger <44003963+MaximilianAnzinger@users.noreply.github.com>
Co-authored-by: Maximilian Anzinger <44003963+MaximilianAnzinger@users.noreply.github.com>
…om:ls1intum/memo into feature/setup-domain-core-db-communication

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds initial seeding capabilities for learning resources and significantly extends the UI with new service layers, repository patterns, and enhanced page designs. The changes implement a clean layered architecture following domain-driven design principles and introduce a comprehensive mapping session interface for competency relationships.

Key Changes:

  • Introduces database seeding infrastructure with demo user and sample competencies/learning resources
  • Implements complete service and repository layers for User, Competency, LearningResource, CompetencyRelationship, and CompetencyResourceLink entities
  • Redesigns the session page with interactive competency mapping, keyboard shortcuts, and live relationship preview
  • Updates the home, about, and navigation components with modern glassmorphic styling and improved UX

Reviewed changes

Copilot reviewed 41 out of 44 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
prisma/seed.js Adds seeding script for demo user, 5 competencies, and 5 learning resources with environment validation
package.json Configures Prisma seed command and updates Next.js to 15.5.9
lib/services/*.ts Implements service layer with business logic for all domain entities
lib/repositories/dc_repo.ts Implements Prisma-based repositories as the sole database access layer
lib/repositories/dc_interface.ts Defines repository contracts for dependency injection
lib/domain/domain_core.ts Defines shared domain models and input types
app/actions/*.ts Implements server actions exposing CRUD operations to client components
app/session/page.tsx Complete rewrite with interactive competency mapping, keyboard shortcuts, relationship type selection, and undo functionality
components/navbar.tsx Updates with centered sliding navigation indicator and improved glassmorphic design
components/footer.tsx Adds new footer component with links and copyright
components/ui/tooltip.tsx Implements custom tooltip component with portal rendering and positioning
components/ui/kbd.tsx Adds keyboard shortcut display component
app/page.tsx Updates home page with session preview image and restructured problem statement section
app/about/page.tsx Redesigns about page with team gallery, course offerings tabs, and FAQ section
app/layout.tsx Wraps all pages with global gradient background and footer
app/globals.css Adds scrollbar styling utilities
Dockerfile Adds prisma generate step to build process
DOMAIN_CORE.md Documents the layered architecture patterns and usage examples
DATABASE.md Adds documentation for seed command

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

return [];
}

const shuffled = [...all].sort(() => Math.random() - 0.5);

Copilot AI Dec 23, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The shuffling algorithm using Math.random() - 0.5 is not a proper shuffle implementation. It has known biases and doesn't provide a uniform distribution. Use the Fisher-Yates shuffle algorithm instead for proper randomization.

Copilot uses AI. Check for mistakes.
Comment thread app/page.tsx
Comment on lines +115 to +118
<div className="absolute inset-0 bg-white" aria-hidden />
<div
className="pointer-events-none absolute inset-0 bg-[linear-gradient(to_bottom,rgba(255,255,255,0)_0%,rgba(255,255,255,1)_20%,rgba(255,255,255,1)_80%,rgba(255,255,255,0)_100%)]"
aria-hidden

Copilot AI Dec 23, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable 'aria-hidden' should be a boolean attribute 'aria-hidden={true}' or 'aria-hidden="true"' instead of just 'aria-hidden' without a value. While browsers may handle this, it's not semantically correct.

Suggested change
<div className="absolute inset-0 bg-white" aria-hidden />
<div
className="pointer-events-none absolute inset-0 bg-[linear-gradient(to_bottom,rgba(255,255,255,0)_0%,rgba(255,255,255,1)_20%,rgba(255,255,255,1)_80%,rgba(255,255,255,0)_100%)]"
aria-hidden
<div className="absolute inset-0 bg-white" aria-hidden="true" />
<div
className="pointer-events-none absolute inset-0 bg-[linear-gradient(to_bottom,rgba(255,255,255,0)_0%,rgba(255,255,255,1)_20%,rgba(255,255,255,1)_80%,rgba(255,255,255,0)_100%)]"
aria-hidden="true"

Copilot uses AI. Check for mistakes.
Comment thread app/page.tsx
Comment on lines +115 to +118
<div className="absolute inset-0 bg-white" aria-hidden />
<div
className="pointer-events-none absolute inset-0 bg-[linear-gradient(to_bottom,rgba(255,255,255,0)_0%,rgba(255,255,255,1)_20%,rgba(255,255,255,1)_80%,rgba(255,255,255,0)_100%)]"
aria-hidden

Copilot AI Dec 23, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable 'aria-hidden' should be a boolean attribute 'aria-hidden={true}' or 'aria-hidden="true"' instead of just 'aria-hidden' without a value. While browsers may handle this, it's not semantically correct.

Suggested change
<div className="absolute inset-0 bg-white" aria-hidden />
<div
className="pointer-events-none absolute inset-0 bg-[linear-gradient(to_bottom,rgba(255,255,255,0)_0%,rgba(255,255,255,1)_20%,rgba(255,255,255,1)_80%,rgba(255,255,255,0)_100%)]"
aria-hidden
<div className="absolute inset-0 bg-white" aria-hidden="true" />
<div
className="pointer-events-none absolute inset-0 bg-[linear-gradient(to_bottom,rgba(255,255,255,0)_0%,rgba(255,255,255,1)_20%,rgba(255,255,255,1)_80%,rgba(255,255,255,0)_100%)]"
aria-hidden="true"

Copilot uses AI. Check for mistakes.
Comment thread app/session/page.tsx
Comment on lines +614 to +743
<div className="flex flex-wrap gap-2 flex-shrink-0">
{/* Placeholder badges; replace with competency metadata once available */}
<Tooltip side="top">
<TooltipTrigger asChild>
<Badge
tabIndex={0}
className="bg-slate-100 text-slate-700 border border-slate-300 cursor-help hover:bg-slate-200 transition-colors"
>
Apply
<Info className="h-3 w-3 ml-1.5" />
</Badge>
</TooltipTrigger>
<TooltipContent>
<p>
Bloom&apos;s Taxonomy categorizes learning
objectives by cognitive level.
</p>
<p className="mt-1">
→ Apply: use knowledge in practice (implement,
execute, solve).
</p>
<p className="mt-1 text-[11px] text-slate-200">
Levels: Remember • Understand • Apply • Analyze
• Evaluate • Create
</p>
</TooltipContent>
</Tooltip>
<Badge className="bg-slate-100 text-slate-700 border border-slate-300">
Control Flow
</Badge>
</div>
<div className="flex-1 flex flex-col min-h-0 space-y-2">
<CardTitle className="text-lg font-bold text-slate-900 flex-shrink-0">
{competencies[0]!.title}
</CardTitle>
<div
className="flex-1 overflow-y-scroll pr-2 scrollbar-thin"
style={SCROLLBAR_STYLES}
>
<CardDescription className="text-base leading-relaxed text-slate-600">
{competencies[0]!.description}
</CardDescription>
</div>
</div>
</CardHeader>
</Card>
)}

{/* Arrow Connection with Swap Button */}
<div className="flex flex-col items-center justify-center z-10 px-4 gap-3">
<div className="flex items-center gap-2">
<div className="h-0.5 w-12 bg-gradient-to-r from-[#0a4da2] to-[#4263eb]" />
<div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-full bg-gradient-to-br from-[#0a4da2] via-[#4263eb] to-[#9775fa] shadow-xl ring-3 ring-white">
<ArrowRight
className="h-6 w-6 text-white"
aria-hidden="true"
/>
<Label
htmlFor={value}
className="text-sm font-normal text-slate-800"
>
{label}
</Label>
</div>
))}
</RadioGroup>
</CardContent>
</Card>
</div>
<div className="h-0.5 w-12 bg-gradient-to-l from-[#9775fa] to-[#5538d1]" />
</div>
{competencies && competencies.length >= 2 && (
<Button
variant="ghost"
size="sm"
onClick={e => {
if (competencies && competencies.length >= 2) {
setCompetencies([
competencies[1]!,
competencies[0]!,
]);
}
// Remove focus after click
(e.currentTarget as HTMLButtonElement).blur();
}}
className="text-slate-600 hover:text-slate-900 hover:bg-slate-100 focus:outline-none"
>
<ArrowLeftRight className="h-4 w-4 mr-1.5" />
Swap Direction
</Button>
)}
</div>

<div className="col-span-1">
<CompetencyCard {...competencies[1]} />
</div>
</div>
{/* Destination Competency */}
{isLoading || !competencies || !competencies[1] ? (
<Card className="border border-slate-200 bg-slate-50/50">
<CardHeader>
<CardTitle className="text-slate-400">
Loading...
</CardTitle>
</CardHeader>
</Card>
) : (
<Card className="relative flex h-[300px] flex-col border-2 border-[#9775fa]/30 bg-gradient-to-br from-purple-50/80 to-white shadow-lg transition-all hover:border-[#9775fa]/50 overflow-hidden">
<div className="absolute left-0 top-0 h-full w-1.5 bg-gradient-to-b from-[#9775fa] to-[#5538d1] rounded-r-full" />
<CardHeader className="flex h-full flex-col space-y-3 pb-3 pl-5 overflow-visible">
<div className="flex-shrink-0">
<Badge className="w-fit bg-[#7c3aed] text-white border-[#7c3aed] font-semibold text-xs px-3 py-1.5">
Competency
</Badge>
</div>
<div className="flex flex-wrap gap-2 flex-shrink-0">
{/* Placeholder badges; replace with competency metadata once available */}
<Tooltip side="top">
<TooltipTrigger asChild>
<Badge
tabIndex={0}
className="bg-slate-100 text-slate-700 border border-slate-300 cursor-help hover:bg-slate-200 transition-colors"
>
Apply
<Info className="h-3 w-3 ml-1.5" />
</Badge>
</TooltipTrigger>
<TooltipContent>
<p>
Bloom&apos;s Taxonomy categorizes learning
objectives by cognitive level.
</p>
<p className="mt-1">
→ Apply: use knowledge in practice (implement,
execute, solve).
</p>
<p className="mt-1 text-[11px] text-slate-200">
Levels: Remember • Understand • Apply • Analyze
• Evaluate • Create
</p>
</TooltipContent>
</Tooltip>
<Badge className="bg-slate-100 text-slate-700 border border-slate-300">
Programming Fundamentals
</Badge>

Copilot AI Dec 23, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These placeholder badge values ('Apply', 'Control Flow', 'Programming Fundamentals') are hardcoded and repeated across both competency cards. They don't reflect the actual competency data and should either be removed, replaced with actual competency metadata, or clearly marked as placeholder/mock data.

Copilot uses AI. Check for mistakes.
Comment thread app/session/page.tsx
Comment on lines +477 to +478
isMountedRef.current = false;
window.removeEventListener('keydown', handleKeyDown);

Copilot AI Dec 23, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The isMountedRef pattern is used to prevent state updates after unmounting, but the cleanup function sets isMountedRef.current = false before the event listener is removed. This could cause a race condition where an event fires during cleanup but after the mounted flag is set to false. Consider removing the event listener first, then setting the mounted flag.

Suggested change
isMountedRef.current = false;
window.removeEventListener('keydown', handleKeyDown);
window.removeEventListener('keydown', handleKeyDown);
isMountedRef.current = false;

Copilot uses AI. Check for mistakes.
Comment thread app/page.tsx
results across recommender systems hard to compare and trust.
</p>
</div>
<button className="w-full max-w-xl rounded-2xl border border-[#d9e6ff] bg-[#e9f1ff] px-6 py-6 text-lg font-semibold text-[#0a4da2] shadow-[0_22px_60px_-36px_rgba(7,30,84,0.35)] transition hover:-translate-y-0.5 hover:shadow-[0_26px_70px_-40px_rgba(7,30,84,0.45)]">

Copilot AI Dec 23, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 'Visualization' button has no onClick handler or proper interactive functionality. If this is a placeholder, consider adding disabled={true} or aria-disabled="true" to indicate it's not yet functional, or add a comment explaining its future purpose.

Suggested change
<button className="w-full max-w-xl rounded-2xl border border-[#d9e6ff] bg-[#e9f1ff] px-6 py-6 text-lg font-semibold text-[#0a4da2] shadow-[0_22px_60px_-36px_rgba(7,30,84,0.35)] transition hover:-translate-y-0.5 hover:shadow-[0_26px_70px_-40px_rgba(7,30,84,0.45)]">
<button
type="button"
disabled
aria-disabled="true"
className="w-full max-w-xl rounded-2xl border border-[#d9e6ff] bg-[#e9f1ff] px-6 py-6 text-lg font-semibold text-[#0a4da2] shadow-[0_22px_60px_-36px_rgba(7,30,84,0.35)] transition hover:-translate-y-0.5 hover:shadow-[0_26px_70px_-40px_rgba(7,30,84,0.45)]"
>

Copilot uses AI. Check for mistakes.
Comment thread app/session/page.tsx
Comment on lines +685 to +686
// Remove focus after click
(e.currentTarget as HTMLButtonElement).blur();

Copilot AI Dec 23, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Swap Direction button uses blur() to remove focus after click, which can negatively impact keyboard navigation and accessibility. Users navigating with keyboard will lose their position. Consider letting the browser handle focus naturally or implementing proper focus management.

Suggested change
// Remove focus after click
(e.currentTarget as HTMLButtonElement).blur();

Copilot uses AI. Check for mistakes.
Comment thread components/navbar.tsx
Comment on lines +73 to +82
{activeIndex >= 0 && (
<div
className="absolute inset-y-1 rounded-full bg-gradient-to-r from-[#0a4da2] to-[#7c6cff] shadow-md transition-all duration-300 ease-out"
style={{
width: 'calc(50% - 1rem)',
left: activeIndex === 1 ? 'calc(50% + 0.5rem)' : '0.5rem',
}}
aria-hidden="true"
/>
)}

Copilot AI Dec 23, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The navbar uses a complex centered sliding navigation pattern with absolute positioning. The activeIndex calculation assumes only 2 navigation items. If additional navigation items are added in the future, the calculation on line 78 (activeIndex === 1 ? 'calc(50% + 0.5rem)' : '0.5rem') will break and needs to be more dynamic to support n items.

Copilot uses AI. Check for mistakes.
Comment thread app/about/page.tsx
Comment on lines +70 to +93
<button className="inline-flex w-fit items-center gap-2 rounded-full bg-slate-900 px-4 py-2 text-sm font-semibold text-white shadow-sm transition hover:-translate-y-0.5 hover:bg-black">
Explore teaching
<span className="inline-block text-base">→</span>
</button>
</div>

<div className="lg:col-span-5 flex flex-col gap-4">
<div className="relative aspect-[4/3] overflow-hidden rounded-3xl border border-slate-200 bg-slate-100 shadow-md flex items-center justify-center text-slate-400 text-sm font-semibold">
Visual placeholder
</div>
<div className="relative overflow-hidden rounded-3xl border border-slate-900 bg-slate-900 p-6 text-white shadow-xl">
<div className="flex items-start justify-between gap-4">
<div className="space-y-3">
<div className="text-xl font-semibold leading-tight">
Meet our lecturers
</div>
<p className="text-sm text-white/70">
Coaches, researchers, and industry mentors guiding student
teams on architecture, AI, and continuous delivery across
TUM courses.
</p>
<button className="inline-flex items-center gap-2 rounded-full border border-white/25 bg-white/10 px-4 py-2 text-sm font-medium transition hover:bg-white/15">
View teaching formats
<span className="text-lg">→</span>

Copilot AI Dec 23, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The buttons on lines 70 and 91 have no onClick handlers or proper interactive functionality. They appear to be placeholders for future navigation. Consider adding disabled={true} or aria-disabled="true" attributes, or implement the actual navigation behavior, or add TODO comments explaining what they should link to.

Copilot uses AI. Check for mistakes.
Comment thread app/layout.tsx
>
<Navbar />
{children}
<div className="relative min-h-screen bg-gradient-to-br from-[#d7e3ff] via-[#f3f5ff] to-[#e8ecff] dark:bg-slate-950">

Copilot AI Dec 23, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The layout applies the same gradient background (from-[#d7e3ff] via-[#f3f5ff] to-[#e8ecff]) to all pages globally. However, individual pages like app/page.tsx and app/about/page.tsx also define the same gradient in their root div elements, causing duplication. Consider removing the gradient from either the layout or individual pages to avoid redundant styling and improve maintainability.

Suggested change
<div className="relative min-h-screen bg-gradient-to-br from-[#d7e3ff] via-[#f3f5ff] to-[#e8ecff] dark:bg-slate-950">
<div className="relative min-h-screen dark:bg-slate-950">

Copilot uses AI. Check for mistakes.
@markstockhausen

Copy link
Copy Markdown
Contributor Author

This PR is obsolete, I created a new PR implementing this functionality

@markstockhausen
markstockhausen deleted the feature/seed-ui-learning-resources branch April 8, 2026 13:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants