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 { NextRequest, NextResponse } from "next/server";
import { RecommendationService } from "@/services/recommendationService"; import { RecommendationService } from "@/services/recommendationService";
import { RecommendationRequest, UserCategory, ExperienceLevel } from "@/types/recommendations"; import { RecommendationRequest, UserCategory } from "@/types/recommendations";
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
try { try {
@@ -79,7 +79,6 @@ export async function POST(request: NextRequest) {
{ {
platform_id: body.platform_id, platform_id: body.platform_id,
categories: body.categories, categories: body.categories,
experienceLevel: body.experienceLevel,
limit, limit,
} }
); );
@@ -90,7 +89,6 @@ export async function POST(request: NextRequest) {
userProfile: { userProfile: {
categories: body.categories, categories: body.categories,
platform: body.platform_id, platform: body.platform_id,
experienceLevel: body.experienceLevel,
}, },
}); });
} catch (error) { } catch (error) {
@@ -110,7 +108,6 @@ export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
const platformId = searchParams.get("platform_id"); const platformId = searchParams.get("platform_id");
const categoriesParam = searchParams.get("categories"); const categoriesParam = searchParams.get("categories");
const experienceLevel = searchParams.get("experience_level");
const limit = searchParams.get("limit"); const limit = searchParams.get("limit");
// Validate required fields // 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 // Set default limit
const parsedLimit = const parsedLimit =
limit && parseInt(limit) > 0 && parseInt(limit) <= 50 limit && parseInt(limit) > 0 && parseInt(limit) <= 50
@@ -196,7 +182,6 @@ export async function GET(request: NextRequest) {
{ {
platform_id: platformId, platform_id: platformId,
categories: categories as UserCategory[], categories: categories as UserCategory[],
experienceLevel: experienceLevel as ExperienceLevel | undefined,
limit: parsedLimit, limit: parsedLimit,
} }
); );
@@ -207,7 +192,6 @@ export async function GET(request: NextRequest) {
userProfile: { userProfile: {
categories, categories,
platform: platformId, platform: platformId,
experienceLevel,
}, },
}); });
} catch (error) { } catch (error) {
+2 -3
View File
@@ -42,7 +42,7 @@ export function RecommendationsSection({
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const [viewMode, setViewMode] = useState<ViewMode>('grid') const [viewMode, setViewMode] = useState<ViewMode>('grid')
const [filterCategory, setFilterCategory] = useState<FilterCategory>('all') const [filterCategory, setFilterCategory] = useState<FilterCategory>('all')
const [isExpanded, setIsExpanded] = useState(false) const [isExpanded, setIsExpanded] = useState(true)
const fetchRecommendations = async () => { const fetchRecommendations = async () => {
if (!isProfileComplete()) { if (!isProfileComplete()) {
@@ -61,7 +61,6 @@ export function RecommendationsSection({
body: JSON.stringify({ body: JSON.stringify({
platform_id: getEffectiveOS(), platform_id: getEffectiveOS(),
categories: profile.categories, categories: profile.categories,
experienceLevel: profile.experienceLevel,
limit: 1000 limit: 1000
}) })
}) })
@@ -104,7 +103,7 @@ export function RecommendationsSection({
// But for now, let's rely on the cache key changing which includes profile data // But for now, let's rely on the cache key changing which includes profile data
fetchRecommendations() fetchRecommendations()
} }
}, [profile.categories, profile.selectedOS, profile.experienceLevel]) }, [profile.categories, profile.selectedOS])
const isPackageSelected = (pkg: RecommendedPackage) => { const isPackageSelected = (pkg: RecommendedPackage) => {
return selectedPackages.some(selected => selected.id === pkg.id) 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 { useLocale } from '@/contexts/LocaleContext'
import { useRecommendationProfile } from '@/hooks/useRecommendationProfile' import { useRecommendationProfile } from '@/hooks/useRecommendationProfile'
import { Platform, Package, SelectedPackage, FilterOptions, GeneratedScript } from '@/types' 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 }) { function RepoHubAppContent({ cryptomusEnabled }: { cryptomusEnabled: boolean }) {
const { t, locale } = useLocale() const { t, locale } = useLocale()
@@ -50,7 +53,8 @@ function RepoHubAppContent({ cryptomusEnabled }: { cryptomusEnabled: boolean })
loadPlatforms() loadPlatforms()
}, []) }, [])
// Show onboarding modal on first visit // Show onboarding modal on first visit - DISABLED as per user request
/*
useEffect(() => { useEffect(() => {
if (!isProfileLoading && !hasCompletedOnboarding) { if (!isProfileLoading && !hasCompletedOnboarding) {
// Delay to allow page to render first // Delay to allow page to render first
@@ -60,18 +64,17 @@ function RepoHubAppContent({ cryptomusEnabled }: { cryptomusEnabled: boolean })
return () => clearTimeout(timer) return () => clearTimeout(timer)
} }
}, [isProfileLoading, hasCompletedOnboarding]) }, [isProfileLoading, hasCompletedOnboarding])
*/
const handleOnboardingComplete = (data: { const handleOnboardingComplete = (data: {
categories: UserCategory[] categories: UserCategory[]
selectedOS?: string selectedOS?: string
experienceLevel: ExperienceLevel
}) => { }) => {
console.log('🎯 Onboarding completed with data:', data) console.log('🎯 Onboarding completed with data:', data)
const success = saveProfile({ const success = saveProfile({
categories: data.categories, categories: data.categories,
selectedOS: data.selectedOS, selectedOS: data.selectedOS,
experienceLevel: data.experienceLevel,
hasCompletedOnboarding: true hasCompletedOnboarding: true
}) })
@@ -192,13 +195,32 @@ function RepoHubAppContent({ cryptomusEnabled }: { cryptomusEnabled: boolean })
{/* Main Content */} {/* Main Content */}
<div className="space-y-8"> <div className="space-y-8">
{/* Recommendations Section - Show if profile is complete */} {/* Recommendations Section - Show if profile is complete */}
{hasCompletedOnboarding && profile.categories.length > 0 && ( {hasCompletedOnboarding && profile.categories.length > 0 ? (
<RecommendationsSection <RecommendationsSection
onPackageToggle={handlePackageToggle} onPackageToggle={handlePackageToggle}
selectedPackages={selectedPackages} selectedPackages={selectedPackages}
onCustomizeClick={handleCustomizePreferences} onCustomizeClick={handleCustomizePreferences}
profile={profile} 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:", reason: "Why recommended:",
based_on: "Based on your interests in:", based_on: "Based on your interests in:",
packages: "packages", 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: { sort: {
recommended: "Best Match", recommended: "Best Match",
popular: "Popular", popular: "Popular",
@@ -378,6 +381,9 @@ const translations = {
reason: "Neden önerildi:", reason: "Neden önerildi:",
based_on: "İlgi alanlarınıza göre:", based_on: "İlgi alanlarınıza göre:",
packages: "paket", 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: { sort: {
recommended: "En Uygun", recommended: "En Uygun",
popular: "Popüler", popular: "Popüler",
+7 -40
View File
@@ -4,7 +4,6 @@ import { useState, useEffect, useCallback } from "react";
import { import {
UserProfile, UserProfile,
UserCategory, UserCategory,
ExperienceLevel,
} from "@/types/recommendations"; } from "@/types/recommendations";
const STORAGE_KEY = "repohub_user_profile"; const STORAGE_KEY = "repohub_user_profile";
@@ -83,46 +82,16 @@ export function useRecommendationProfile() {
// Load profile from localStorage on mount // Load profile from localStorage on mount
useEffect(() => { useEffect(() => {
try { // We intentionally do NOT load from localStorage anymore to reset on refresh
const stored = localStorage.getItem(STORAGE_KEY); // as requested by user preference change.
if (stored) {
const parsed = JSON.parse(stored) as UserProfile;
// Handle version migration // Initialize with default profile (detects OS)
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(); const defaultProfile = getDefaultProfile();
setProfile(defaultProfile); setProfile(defaultProfile);
localStorage.setItem(STORAGE_KEY, JSON.stringify(defaultProfile));
}
} catch (error) {
console.error("Error loading user profile:", error);
} finally {
setIsLoading(false); setIsLoading(false);
}
}, []); }, []);
// Save profile to localStorage // Save profile to state only (session persistence)
const saveProfile = useCallback( const saveProfile = useCallback(
(newProfile: Partial<UserProfile>) => { (newProfile: Partial<UserProfile>) => {
try { try {
@@ -133,12 +102,10 @@ export function useRecommendationProfile() {
lastUpdated: new Date().toISOString(), lastUpdated: new Date().toISOString(),
}; };
console.log("💾 Saving profile:", updated); console.log("💾 Saving profile (Session only):", updated);
setProfile(updated); setProfile(updated);
localStorage.setItem(STORAGE_KEY, JSON.stringify(updated)); // localStorage.setItem(STORAGE_KEY, JSON.stringify(updated)); // Disabled persistence
console.log("✅ Profile saved successfully to localStorage");
return true; return true;
} catch (error) { } catch (error) {
@@ -177,7 +144,7 @@ export function useRecommendationProfile() {
try { try {
const defaultProfile = getDefaultProfile(); const defaultProfile = getDefaultProfile();
setProfile(defaultProfile); setProfile(defaultProfile);
localStorage.setItem(STORAGE_KEY, JSON.stringify(defaultProfile)); // localStorage.setItem(STORAGE_KEY, JSON.stringify(defaultProfile)); // Disabled persistence
return true; return true;
} catch (error) { } catch (error) {
console.error("Error resetting user profile:", error); console.error("Error resetting user profile:", error);
-8
View File
@@ -12,11 +12,6 @@ export type UserCategory =
| "productivity" | "productivity"
| "education"; | "education";
/**
* User experience level
*/
export type ExperienceLevel = "beginner" | "intermediate" | "advanced";
/** /**
* User profile stored in localStorage * User profile stored in localStorage
*/ */
@@ -25,7 +20,6 @@ export interface UserProfile {
categories: UserCategory[]; categories: UserCategory[];
detectedOS?: string; detectedOS?: string;
selectedOS?: string; // Manual override selectedOS?: string; // Manual override
experienceLevel?: ExperienceLevel;
hasCompletedOnboarding: boolean; hasCompletedOnboarding: boolean;
createdAt: string; createdAt: string;
lastUpdated: string; lastUpdated: string;
@@ -37,7 +31,6 @@ export interface UserProfile {
export interface RecommendationRequest { export interface RecommendationRequest {
platform_id: string; platform_id: string;
categories: UserCategory[]; categories: UserCategory[];
experienceLevel?: ExperienceLevel;
limit?: number; limit?: number;
} }
@@ -76,7 +69,6 @@ export interface PackagePreset {
platforms: string[]; // ['windows', 'macos', 'ubuntu', 'arch', 'fedora'] platforms: string[]; // ['windows', 'macos', 'ubuntu', 'arch', 'fedora']
priority: number; // 1-10, higher = more important priority: number; // 1-10, higher = more important
reason: string; // Why this package is recommended reason: string; // Why this package is recommended
experienceLevel?: ExperienceLevel[]; // Target experience levels
} }
/** /**