mirror of
https://github.com/yusufipk/RepoHub.git
synced 2026-09-11 10:36:07 +00:00
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:
@@ -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) {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
// Initialize with default profile (detects OS)
|
||||||
|
const defaultProfile = getDefaultProfile();
|
||||||
// Handle version migration
|
setProfile(defaultProfile);
|
||||||
if (!parsed.version || parsed.version < CURRENT_PROFILE_VERSION) {
|
setIsLoading(false);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 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);
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user