refactor: Remove experience level from recommendation system

- Remove experienceLevel from UserProfile, RecommendationRequest, and PackagePreset types
- Remove experience level validation and handling from GET/POST endpoints
- Disable localStorage persistence in useRecommendationProfile hook (session-only storage)
- Disable automatic onboarding modal on first visit
- Set recommendations section to expanded by default
- Add empty state card for recommendations with wizard button
- Add translations
This commit is contained in:
Yusuf İpek
2025-11-23 14:55:49 +03:00
parent a5b65bb4df
commit 95ad5e32fd
6 changed files with 47 additions and 77 deletions
+1 -17
View File
@@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
import { RecommendationService } from "@/services/recommendationService";
import { RecommendationRequest, UserCategory, ExperienceLevel } from "@/types/recommendations";
import { RecommendationRequest, UserCategory } from "@/types/recommendations";
export async function POST(request: NextRequest) {
try {
@@ -79,7 +79,6 @@ export async function POST(request: NextRequest) {
{
platform_id: body.platform_id,
categories: body.categories,
experienceLevel: body.experienceLevel,
limit,
}
);
@@ -90,7 +89,6 @@ export async function POST(request: NextRequest) {
userProfile: {
categories: body.categories,
platform: body.platform_id,
experienceLevel: body.experienceLevel,
},
});
} catch (error) {
@@ -110,7 +108,6 @@ export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const platformId = searchParams.get("platform_id");
const categoriesParam = searchParams.get("categories");
const experienceLevel = searchParams.get("experience_level");
const limit = searchParams.get("limit");
// Validate required fields
@@ -174,17 +171,6 @@ export async function GET(request: NextRequest) {
);
}
// Validate experience level if provided
const validExperienceLevels: ExperienceLevel[] = ["beginner", "intermediate", "advanced"];
if (experienceLevel && !validExperienceLevels.includes(experienceLevel as ExperienceLevel)) {
return NextResponse.json(
{
error: `Invalid experience_level. Must be one of: ${validExperienceLevels.join(", ")}`,
},
{ status: 400 }
);
}
// Set default limit
const parsedLimit =
limit && parseInt(limit) > 0 && parseInt(limit) <= 50
@@ -196,7 +182,6 @@ export async function GET(request: NextRequest) {
{
platform_id: platformId,
categories: categories as UserCategory[],
experienceLevel: experienceLevel as ExperienceLevel | undefined,
limit: parsedLimit,
}
);
@@ -207,7 +192,6 @@ export async function GET(request: NextRequest) {
userProfile: {
categories,
platform: platformId,
experienceLevel,
},
});
} catch (error) {
+2 -3
View File
@@ -42,7 +42,7 @@ export function RecommendationsSection({
const [error, setError] = useState<string | null>(null)
const [viewMode, setViewMode] = useState<ViewMode>('grid')
const [filterCategory, setFilterCategory] = useState<FilterCategory>('all')
const [isExpanded, setIsExpanded] = useState(false)
const [isExpanded, setIsExpanded] = useState(true)
const fetchRecommendations = async () => {
if (!isProfileComplete()) {
@@ -61,7 +61,6 @@ export function RecommendationsSection({
body: JSON.stringify({
platform_id: getEffectiveOS(),
categories: profile.categories,
experienceLevel: profile.experienceLevel,
limit: 1000
})
})
@@ -104,7 +103,7 @@ export function RecommendationsSection({
// But for now, let's rely on the cache key changing which includes profile data
fetchRecommendations()
}
}, [profile.categories, profile.selectedOS, profile.experienceLevel])
}, [profile.categories, profile.selectedOS])
const isPackageSelected = (pkg: RecommendedPackage) => {
return selectedPackages.some(selected => selected.id === pkg.id)
+27 -5
View File
@@ -13,7 +13,10 @@ import { generateScript } from '@/lib/scriptGenerator'
import { useLocale } from '@/contexts/LocaleContext'
import { useRecommendationProfile } from '@/hooks/useRecommendationProfile'
import { Platform, Package, SelectedPackage, FilterOptions, GeneratedScript } from '@/types'
import { UserCategory, ExperienceLevel } from '@/types/recommendations'
import { UserCategory } from '@/types/recommendations'
import { Sparkles, Settings, Package as PackageIcon } from 'lucide-react'
import { Card, CardContent } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
function RepoHubAppContent({ cryptomusEnabled }: { cryptomusEnabled: boolean }) {
const { t, locale } = useLocale()
@@ -50,7 +53,8 @@ function RepoHubAppContent({ cryptomusEnabled }: { cryptomusEnabled: boolean })
loadPlatforms()
}, [])
// Show onboarding modal on first visit
// Show onboarding modal on first visit - DISABLED as per user request
/*
useEffect(() => {
if (!isProfileLoading && !hasCompletedOnboarding) {
// Delay to allow page to render first
@@ -60,18 +64,17 @@ function RepoHubAppContent({ cryptomusEnabled }: { cryptomusEnabled: boolean })
return () => clearTimeout(timer)
}
}, [isProfileLoading, hasCompletedOnboarding])
*/
const handleOnboardingComplete = (data: {
categories: UserCategory[]
selectedOS?: string
experienceLevel: ExperienceLevel
}) => {
console.log('🎯 Onboarding completed with data:', data)
const success = saveProfile({
categories: data.categories,
selectedOS: data.selectedOS,
experienceLevel: data.experienceLevel,
hasCompletedOnboarding: true
})
@@ -192,13 +195,32 @@ function RepoHubAppContent({ cryptomusEnabled }: { cryptomusEnabled: boolean })
{/* Main Content */}
<div className="space-y-8">
{/* Recommendations Section - Show if profile is complete */}
{hasCompletedOnboarding && profile.categories.length > 0 && (
{hasCompletedOnboarding && profile.categories.length > 0 ? (
<RecommendationsSection
onPackageToggle={handlePackageToggle}
selectedPackages={selectedPackages}
onCustomizeClick={handleCustomizePreferences}
profile={profile}
/>
) : (
/* Empty State for Recommendations */
<Card className="w-full bg-gradient-to-r from-primary/5 to-secondary/5 border-dashed">
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
<div className="bg-background p-4 rounded-full shadow-sm mb-4">
<Sparkles className="h-8 w-8 text-primary" />
</div>
<h3 className="text-xl font-semibold mb-2">
{t('recommendations.empty_title') || 'Get Personalized Recommendations'}
</h3>
<p className="text-muted-foreground max-w-md mb-6">
{t('recommendations.empty_description') || 'Tell us about your role and platform to get a curated list of essential packages.'}
</p>
<Button onClick={handleCustomizePreferences} size="lg">
<Sparkles className="h-4 w-4 mr-2" />
{t('recommendations.start') || 'Start Recommendation Wizard'}
</Button>
</CardContent>
</Card>
)}
+6
View File
@@ -188,6 +188,9 @@ const translations = {
reason: "Why recommended:",
based_on: "Based on your interests in:",
packages: "packages",
empty_title: "Get Personalized Recommendations",
empty_description: "Tell us about your role and platform to get a curated list of essential packages.",
start: "Start Recommendation Wizard",
sort: {
recommended: "Best Match",
popular: "Popular",
@@ -378,6 +381,9 @@ const translations = {
reason: "Neden önerildi:",
based_on: "İlgi alanlarınıza göre:",
packages: "paket",
empty_title: "Kişiselleştirilmiş Öneriler Alın",
empty_description: "Size temel paketlerden oluşan bir liste sunmamız için kategorileri ve platformunuzu belirtin.",
start: "Öneri Sihirbazını Başlat",
sort: {
recommended: "En Uygun",
popular: "Popüler",
+11 -44
View File
@@ -4,7 +4,6 @@ import { useState, useEffect, useCallback } from "react";
import {
UserProfile,
UserCategory,
ExperienceLevel,
} from "@/types/recommendations";
const STORAGE_KEY = "repohub_user_profile";
@@ -83,46 +82,16 @@ export function useRecommendationProfile() {
// Load profile from localStorage on mount
useEffect(() => {
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
const parsed = JSON.parse(stored) as UserProfile;
// Handle version migration
if (!parsed.version || parsed.version < CURRENT_PROFILE_VERSION) {
console.log(
"Migrating profile from version",
parsed.version || 0,
"to",
CURRENT_PROFILE_VERSION
);
// Add migration logic here when schema changes in the future
parsed.version = CURRENT_PROFILE_VERSION;
}
// Update detectedOS if it changed
const currentOS = detectOS();
if (parsed.detectedOS !== currentOS) {
parsed.detectedOS = currentOS;
}
setProfile(parsed);
// Save migrated profile
localStorage.setItem(STORAGE_KEY, JSON.stringify(parsed));
} else {
// First time user - save default profile
const defaultProfile = getDefaultProfile();
setProfile(defaultProfile);
localStorage.setItem(STORAGE_KEY, JSON.stringify(defaultProfile));
}
} catch (error) {
console.error("Error loading user profile:", error);
} finally {
setIsLoading(false);
}
// We intentionally do NOT load from localStorage anymore to reset on refresh
// as requested by user preference change.
// Initialize with default profile (detects OS)
const defaultProfile = getDefaultProfile();
setProfile(defaultProfile);
setIsLoading(false);
}, []);
// Save profile to localStorage
// Save profile to state only (session persistence)
const saveProfile = useCallback(
(newProfile: Partial<UserProfile>) => {
try {
@@ -133,12 +102,10 @@ export function useRecommendationProfile() {
lastUpdated: new Date().toISOString(),
};
console.log("💾 Saving profile:", updated);
console.log("💾 Saving profile (Session only):", updated);
setProfile(updated);
localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
console.log("✅ Profile saved successfully to localStorage");
// localStorage.setItem(STORAGE_KEY, JSON.stringify(updated)); // Disabled persistence
return true;
} catch (error) {
@@ -177,7 +144,7 @@ export function useRecommendationProfile() {
try {
const defaultProfile = getDefaultProfile();
setProfile(defaultProfile);
localStorage.setItem(STORAGE_KEY, JSON.stringify(defaultProfile));
// localStorage.setItem(STORAGE_KEY, JSON.stringify(defaultProfile)); // Disabled persistence
return true;
} catch (error) {
console.error("Error resetting user profile:", error);
-8
View File
@@ -12,11 +12,6 @@ export type UserCategory =
| "productivity"
| "education";
/**
* User experience level
*/
export type ExperienceLevel = "beginner" | "intermediate" | "advanced";
/**
* User profile stored in localStorage
*/
@@ -25,7 +20,6 @@ export interface UserProfile {
categories: UserCategory[];
detectedOS?: string;
selectedOS?: string; // Manual override
experienceLevel?: ExperienceLevel;
hasCompletedOnboarding: boolean;
createdAt: string;
lastUpdated: string;
@@ -37,7 +31,6 @@ export interface UserProfile {
export interface RecommendationRequest {
platform_id: string;
categories: UserCategory[];
experienceLevel?: ExperienceLevel;
limit?: number;
}
@@ -76,7 +69,6 @@ export interface PackagePreset {
platforms: string[]; // ['windows', 'macos', 'ubuntu', 'arch', 'fedora']
priority: number; // 1-10, higher = more important
reason: string; // Why this package is recommended
experienceLevel?: ExperienceLevel[]; // Target experience levels
}
/**