-
+
{/* Header */}
@@ -79,6 +191,15 @@ function RepoHubAppContent({ cryptomusEnabled }: { cryptomusEnabled: boolean })
{/* Main Content */}
+ {/* Recommendations Section - Show if profile is complete */}
+ {hasCompletedOnboarding && profile.categories.length > 0 && (
+
+ )}
+
{/* Platform Selector */}
)}
+
+ {/* Onboarding Modal */}
+
setShowOnboarding(false)}
+ onComplete={handleOnboardingComplete}
+ detectedOS={detectedOS || 'unknown'}
+ />
)
diff --git a/src/contexts/LocaleContext.tsx b/src/contexts/LocaleContext.tsx
index 40e3553..3233454 100644
--- a/src/contexts/LocaleContext.tsx
+++ b/src/contexts/LocaleContext.tsx
@@ -10,7 +10,11 @@ const translations = {
title: "RepoHub - Cross-Platform Package Manager",
subtitle: "Cross-Platform Package Manager",
description: "Simplify software installation across Linux, Windows, and macOS with official repositories",
- close: "Close"
+ close: "Close",
+ next: "Next",
+ back: "Back",
+ select_all: "Select All",
+ deselect_all: "Deselect All"
},
platform: {
select: "Select Your Platform",
@@ -106,6 +110,90 @@ const translations = {
success_note1: "Payment confirmation will be sent to your email if provided.",
success_note2: "You can close this page and return to RepoHub.",
back_to_site: "Back to RepoHub"
+ },
+ onboarding: {
+ title: "Welcome to RepoHub!",
+ subtitle: "Let's personalize your package recommendations",
+ complete: "Get My Recommendations",
+ step1: {
+ title: "What do you want to use your computer for?",
+ description: "Select up to 3 categories that match your needs",
+ selected: "{count} selected (max 3)",
+ all_selected: "All {count} categories selected"
+ },
+ step2: {
+ title: "Select your operating system",
+ description: "We'll recommend compatible packages for your platform",
+ detected: "✓ Detected: {os}"
+ },
+ step3: {
+ title: "What's your experience level?",
+ description: "This helps us recommend appropriate tools",
+ levels: {
+ beginner: {
+ name: "Beginner",
+ description: "New to software installation and package management"
+ },
+ intermediate: {
+ name: "Intermediate",
+ description: "Comfortable with basic command-line operations"
+ },
+ advanced: {
+ name: "Advanced",
+ description: "Experienced with system administration and package management"
+ }
+ }
+ }
+ },
+ categories: {
+ development: {
+ name: "Development",
+ description: "Code editors, compilers, and development tools"
+ },
+ design: {
+ name: "Design",
+ description: "Graphics, UI/UX, and creative software"
+ },
+ multimedia: {
+ name: "Multimedia",
+ description: "Audio, video editing, and media tools"
+ },
+ "system-tools": {
+ name: "System Tools",
+ description: "System administration and utilities"
+ },
+ gaming: {
+ name: "Gaming",
+ description: "Gaming platforms and related tools"
+ },
+ productivity: {
+ name: "Productivity",
+ description: "Office, note-taking, and productivity apps"
+ },
+ education: {
+ name: "Education",
+ description: "Educational and scientific software"
+ }
+ },
+ recommendations: {
+ title: "Recommended for You",
+ subtitle: "Personalized package recommendations based on your profile",
+ refresh: "Refresh Recommendations",
+ customize: "Customize Preferences",
+ no_recommendations: "No recommendations available. Please update your preferences.",
+ loading: "Finding the best packages for you...",
+ score: "Match Score",
+ preset_badge: "Essential",
+ view_details: "View Details",
+ add_to_selection: "Add to Selection",
+ reason: "Why recommended:",
+ based_on: "Based on your interests in:",
+ packages: "packages",
+ sort: {
+ recommended: "Best Match",
+ popular: "Popular",
+ preset: "Essential"
+ }
}
},
tr: {
@@ -113,7 +201,11 @@ const translations = {
title: "RepoHub - Çok Platformlu Paket Yöneticisi",
subtitle: "Çok Platformlu Paket Yöneticisi",
description: "Linux, Windows ve macOS'te resmi depoları kullanarak yazılım kurulumunu basitleştirin",
- close: "Kapat"
+ close: "Kapat",
+ next: "İleri",
+ back: "Geri",
+ select_all: "Tümünü Seç",
+ deselect_all: "Seçimi Kaldır"
},
platform: {
select: "Platformunuzu Seçin",
@@ -209,6 +301,90 @@ const translations = {
success_note1: "Ödeme onayı sağlandıysa e-postanıza gönderilecektir.",
success_note2: "Bu sayfayı kapatabilir ve RepoHub'a dönebilirsiniz.",
back_to_site: "RepoHub'a Geri Dön"
+ },
+ onboarding: {
+ title: "RepoHub'a Hoş Geldiniz!",
+ subtitle: "Paket önerilerinizi kişiselleştirelim",
+ complete: "Önerilerimi Getir",
+ step1: {
+ title: "Bilgisayarınızı ne için kullanmak istiyorsunuz?",
+ description: "İhtiyaçlarınıza uygun en fazla 3 kategori seçin",
+ selected: "{count} seçildi (max 3)",
+ all_selected: "Tüm {count} kategori seçildi"
+ },
+ step2: {
+ title: "İşletim sisteminizi seçin",
+ description: "Platformunuzla uyumlu paketler önereceğiz",
+ detected: "✓ Tespit edildi: {os}"
+ },
+ step3: {
+ title: "Deneyim seviyeniz nedir?",
+ description: "Bu, size uygun araçları önermemize yardımcı olur",
+ levels: {
+ beginner: {
+ name: "Başlangıç",
+ description: "Yazılım kurulumu ve paket yönetimine yeni"
+ },
+ intermediate: {
+ name: "Orta",
+ description: "Temel komut satırı işlemlerinde rahat"
+ },
+ advanced: {
+ name: "İleri",
+ description: "Sistem yönetimi ve paket yönetiminde deneyimli"
+ }
+ }
+ }
+ },
+ categories: {
+ development: {
+ name: "Geliştirme",
+ description: "Kod editörleri, derleyiciler ve geliştirme araçları"
+ },
+ design: {
+ name: "Tasarım",
+ description: "Grafik, UI/UX ve yaratıcı yazılımlar"
+ },
+ multimedia: {
+ name: "Multimedya",
+ description: "Ses, video düzenleme ve medya araçları"
+ },
+ "system-tools": {
+ name: "Sistem Araçları",
+ description: "Sistem yönetimi ve yardımcı programlar"
+ },
+ gaming: {
+ name: "Oyun",
+ description: "Oyun platformları ve ilgili araçlar"
+ },
+ productivity: {
+ name: "Üretkenlik",
+ description: "Ofis, not alma ve üretkenlik uygulamaları"
+ },
+ education: {
+ name: "Eğitim",
+ description: "Eğitim ve bilimsel yazılımlar"
+ }
+ },
+ recommendations: {
+ title: "Size Özel Öneriler",
+ subtitle: "Profilinize göre kişiselleştirilmiş paket önerileri",
+ refresh: "Önerileri Yenile",
+ customize: "Tercihleri Özelleştir",
+ no_recommendations: "Öneri mevcut değil. Lütfen tercihlerinizi güncelleyin.",
+ loading: "Sizin için en iyi paketleri buluyoruz...",
+ score: "Eşleşme Skoru",
+ preset_badge: "Temel",
+ view_details: "Detayları Gör",
+ add_to_selection: "Seçime Ekle",
+ reason: "Neden önerildi:",
+ based_on: "İlgi alanlarınıza göre:",
+ packages: "paket",
+ sort: {
+ recommended: "En Uygun",
+ popular: "Popüler",
+ preset: "Temel"
+ }
}
}
}
@@ -238,7 +414,7 @@ export function LocaleProvider({ children }: { children: ReactNode }) {
if (browserLang.startsWith('tr')) {
return 'tr'
}
-
+
return 'en'
}
@@ -257,7 +433,7 @@ export function LocaleProvider({ children }: { children: ReactNode }) {
const t = (key: string, params?: Record
) => {
const keys = key.split('.')
let value: any = translations[locale]
-
+
for (const k of keys) {
value = value?.[k]
}
diff --git a/src/data/recommendationPresets.ts b/src/data/recommendationPresets.ts
new file mode 100644
index 0000000..fb36e7b
--- /dev/null
+++ b/src/data/recommendationPresets.ts
@@ -0,0 +1,410 @@
+import { CategoryPreset } from "@/types/recommendations";
+
+/**
+ * Curated package recommendations for each user category
+ * These presets are used by the recommendation engine to suggest packages
+ */
+export const RECOMMENDATION_PRESETS: CategoryPreset[] = [
+ {
+ category: "development",
+ description: "Essential tools for software development",
+ icon: "💻",
+ packages: [
+ {
+ packageName: "git",
+ platforms: ["windows", "macos", "ubuntu", "debian", "arch", "fedora"],
+ priority: 10,
+ reason: "Version control system essential for all developers",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ {
+ packageName: "code",
+ platforms: ["windows", "macos", "ubuntu", "debian"],
+ priority: 9,
+ reason: "Visual Studio Code - Popular code editor",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ {
+ packageName: "visual-studio-code",
+ platforms: ["arch", "fedora"],
+ priority: 9,
+ reason: "Visual Studio Code - Popular code editor",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ {
+ packageName: "nodejs",
+ platforms: ["windows", "macos", "ubuntu", "debian", "arch", "fedora"],
+ priority: 8,
+ reason: "JavaScript runtime for modern web development",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ {
+ packageName: "python3",
+ platforms: ["ubuntu", "debian", "arch", "fedora"],
+ priority: 8,
+ reason: "Python programming language",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ {
+ packageName: "python",
+ platforms: ["windows", "macos"],
+ priority: 8,
+ reason: "Python programming language",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ {
+ packageName: "docker",
+ platforms: ["windows", "macos", "ubuntu", "debian", "arch", "fedora"],
+ priority: 7,
+ reason: "Containerization platform for development",
+ experienceLevel: ["intermediate", "advanced"],
+ },
+ {
+ packageName: "curl",
+ platforms: ["ubuntu", "debian", "arch", "fedora", "macos"],
+ priority: 7,
+ reason: "Command-line tool for transferring data",
+ experienceLevel: ["intermediate", "advanced"],
+ },
+ {
+ packageName: "vim",
+ platforms: ["ubuntu", "debian", "arch", "fedora", "macos"],
+ priority: 6,
+ reason: "Powerful text editor",
+ experienceLevel: ["intermediate", "advanced"],
+ },
+ {
+ packageName: "postman",
+ platforms: ["windows", "macos", "ubuntu", "debian"],
+ priority: 6,
+ reason: "API development and testing tool",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ ],
+ },
+ {
+ category: "design",
+ description: "Tools for graphic design, UI/UX, and creative work",
+ icon: "🎨",
+ packages: [
+ {
+ packageName: "gimp",
+ platforms: ["windows", "macos", "ubuntu", "debian", "arch", "fedora"],
+ priority: 9,
+ reason: "Free and open-source image editor",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ {
+ packageName: "inkscape",
+ platforms: ["windows", "macos", "ubuntu", "debian", "arch", "fedora"],
+ priority: 8,
+ reason: "Professional vector graphics editor",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ {
+ packageName: "blender",
+ platforms: ["windows", "macos", "ubuntu", "debian", "arch", "fedora"],
+ priority: 8,
+ reason: "3D creation suite",
+ experienceLevel: ["intermediate", "advanced"],
+ },
+ {
+ packageName: "figma",
+ platforms: ["windows", "macos"],
+ priority: 9,
+ reason: "Collaborative interface design tool",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ {
+ packageName: "krita",
+ platforms: ["windows", "macos", "ubuntu", "debian", "arch", "fedora"],
+ priority: 7,
+ reason: "Digital painting application",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ ],
+ },
+ {
+ category: "multimedia",
+ description: "Audio, video editing and media management tools",
+ icon: "🎬",
+ packages: [
+ {
+ packageName: "vlc",
+ platforms: ["windows", "macos", "ubuntu", "debian", "arch", "fedora"],
+ priority: 10,
+ reason: "Versatile media player",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ {
+ packageName: "obs-studio",
+ platforms: ["windows", "macos", "ubuntu", "debian", "arch", "fedora"],
+ priority: 9,
+ reason: "Video recording and live streaming",
+ experienceLevel: ["intermediate", "advanced"],
+ },
+ {
+ packageName: "audacity",
+ platforms: ["windows", "macos", "ubuntu", "debian", "arch", "fedora"],
+ priority: 8,
+ reason: "Audio editing software",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ {
+ packageName: "ffmpeg",
+ platforms: ["ubuntu", "debian", "arch", "fedora", "macos"],
+ priority: 8,
+ reason: "Complete multimedia framework",
+ experienceLevel: ["intermediate", "advanced"],
+ },
+ {
+ packageName: "handbrake",
+ platforms: ["windows", "macos", "ubuntu", "debian", "arch", "fedora"],
+ priority: 7,
+ reason: "Video transcoder",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ {
+ packageName: "kdenlive",
+ platforms: ["ubuntu", "debian", "arch", "fedora"],
+ priority: 7,
+ reason: "Video editing software",
+ experienceLevel: ["intermediate", "advanced"],
+ },
+ ],
+ },
+ {
+ category: "system-tools",
+ description: "System administration, security and utilities",
+ icon: "⚙️",
+ packages: [
+ {
+ packageName: "htop",
+ platforms: ["ubuntu", "debian", "arch", "fedora", "macos"],
+ priority: 9,
+ reason: "Interactive process viewer",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ {
+ packageName: "tmux",
+ platforms: ["ubuntu", "debian", "arch", "fedora", "macos"],
+ priority: 8,
+ reason: "Terminal multiplexer",
+ experienceLevel: ["intermediate", "advanced"],
+ },
+ {
+ packageName: "wget",
+ platforms: ["ubuntu", "debian", "arch", "fedora", "macos"],
+ priority: 8,
+ reason: "Network downloader",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ {
+ packageName: "neofetch",
+ platforms: ["ubuntu", "debian", "arch", "fedora", "macos"],
+ priority: 6,
+ reason: "System information tool",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ {
+ packageName: "wireshark",
+ platforms: ["windows", "macos", "ubuntu", "debian", "arch", "fedora"],
+ priority: 7,
+ reason: "Network protocol analyzer",
+ experienceLevel: ["advanced"],
+ },
+ {
+ packageName: "gparted",
+ platforms: ["ubuntu", "debian", "arch", "fedora"],
+ priority: 6,
+ reason: "Partition editor",
+ experienceLevel: ["intermediate", "advanced"],
+ },
+ ],
+ },
+ {
+ category: "gaming",
+ description: "Gaming platforms and related tools",
+ icon: "🎮",
+ packages: [
+ {
+ packageName: "steam",
+ platforms: ["windows", "macos", "ubuntu", "debian", "arch", "fedora"],
+ priority: 10,
+ reason: "Gaming platform",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ {
+ packageName: "discord",
+ platforms: ["windows", "macos", "ubuntu", "debian", "arch", "fedora"],
+ priority: 9,
+ reason: "Voice and chat for gamers",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ {
+ packageName: "lutris",
+ platforms: ["ubuntu", "debian", "arch", "fedora"],
+ priority: 7,
+ reason: "Open gaming platform",
+ experienceLevel: ["intermediate", "advanced"],
+ },
+ {
+ packageName: "wine",
+ platforms: ["ubuntu", "debian", "arch", "fedora", "macos"],
+ priority: 6,
+ reason: "Windows compatibility layer",
+ experienceLevel: ["advanced"],
+ },
+ ],
+ },
+ {
+ category: "productivity",
+ description: "Office, note-taking and productivity tools",
+ icon: "📝",
+ packages: [
+ {
+ packageName: "libreoffice",
+ platforms: ["windows", "macos", "ubuntu", "debian", "arch", "fedora"],
+ priority: 10,
+ reason: "Free office suite",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ {
+ packageName: "thunderbird",
+ platforms: ["windows", "macos", "ubuntu", "debian", "arch", "fedora"],
+ priority: 8,
+ reason: "Email client",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ {
+ packageName: "notion",
+ platforms: ["windows", "macos"],
+ priority: 9,
+ reason: "All-in-one workspace",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ {
+ packageName: "obsidian",
+ platforms: ["windows", "macos", "ubuntu", "debian"],
+ priority: 8,
+ reason: "Knowledge base and note-taking",
+ experienceLevel: ["intermediate", "advanced"],
+ },
+ {
+ packageName: "keepassxc",
+ platforms: ["windows", "macos", "ubuntu", "debian", "arch", "fedora"],
+ priority: 7,
+ reason: "Password manager",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ ],
+ },
+ {
+ category: "education",
+ description: "Educational and scientific software",
+ icon: "🎓",
+ packages: [
+ {
+ packageName: "anki",
+ platforms: ["windows", "macos", "ubuntu", "debian", "arch", "fedora"],
+ priority: 9,
+ reason: "Flashcard application for learning",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ {
+ packageName: "stellarium",
+ platforms: ["windows", "macos", "ubuntu", "debian", "arch", "fedora"],
+ priority: 7,
+ reason: "Planetarium software",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ {
+ packageName: "octave",
+ platforms: ["windows", "macos", "ubuntu", "debian", "arch", "fedora"],
+ priority: 7,
+ reason: "Scientific programming language",
+ experienceLevel: ["intermediate", "advanced"],
+ },
+ {
+ packageName: "geogebra",
+ platforms: ["windows", "macos", "ubuntu", "debian"],
+ priority: 8,
+ reason: "Interactive mathematics software",
+ experienceLevel: ["beginner", "intermediate", "advanced"],
+ },
+ ],
+ },
+];
+
+/**
+ * Get presets for specific categories
+ */
+export function getPresetsForCategories(
+ categories: string[]
+): CategoryPreset[] {
+ return RECOMMENDATION_PRESETS.filter((preset) =>
+ categories.includes(preset.category)
+ );
+}
+
+/**
+ * Get all package names from presets for a specific platform
+ */
+export function getPresetPackageNames(
+ categories: string[],
+ platformId: string
+): string[] {
+ const presets = getPresetsForCategories(categories);
+ const packageNames = new Set();
+
+ presets.forEach((preset) => {
+ preset.packages.forEach((pkg) => {
+ if (pkg.platforms.includes(platformId)) {
+ packageNames.add(pkg.packageName);
+ }
+ });
+ });
+
+ return Array.from(packageNames);
+}
+
+/**
+ * Get preset priority for a package
+ */
+export function getPresetPriority(
+ packageName: string,
+ categories: string[],
+ platformId: string
+): number | null {
+ const presets = getPresetsForCategories(categories);
+
+ for (const preset of presets) {
+ const pkg = preset.packages.find(
+ (p) => p.packageName === packageName && p.platforms.includes(platformId)
+ );
+ if (pkg) {
+ return pkg.priority;
+ }
+ }
+
+ return null;
+}
+
+/**
+ * Get recommendation reason for a package
+ */
+export function getRecommendationReason(
+ packageName: string,
+ categories: string[]
+): string | null {
+ const presets = getPresetsForCategories(categories);
+
+ for (const preset of presets) {
+ const pkg = preset.packages.find((p) => p.packageName === packageName);
+ if (pkg) {
+ return pkg.reason;
+ }
+ }
+
+ return null;
+}
diff --git a/src/hooks/useRecommendationProfile.ts b/src/hooks/useRecommendationProfile.ts
new file mode 100644
index 0000000..52a1f1d
--- /dev/null
+++ b/src/hooks/useRecommendationProfile.ts
@@ -0,0 +1,219 @@
+"use client";
+
+import { useState, useEffect, useCallback } from "react";
+import {
+ UserProfile,
+ UserCategory,
+ ExperienceLevel,
+} from "@/types/recommendations";
+
+const STORAGE_KEY = "repohub_user_profile";
+
+/**
+ * Detect user's operating system from browser
+ */
+function detectOS(): string {
+ if (typeof window === "undefined") {
+ return "unknown";
+ }
+
+ const userAgent = window.navigator.userAgent.toLowerCase();
+ const platform = window.navigator.platform.toLowerCase();
+
+ // Windows
+ if (userAgent.indexOf("win") !== -1 || platform.indexOf("win") !== -1) {
+ return "windows";
+ }
+
+ // macOS
+ if (
+ userAgent.indexOf("mac") !== -1 ||
+ platform.indexOf("mac") !== -1 ||
+ userAgent.indexOf("darwin") !== -1
+ ) {
+ return "macos";
+ }
+
+ // Linux distros
+ if (userAgent.indexOf("linux") !== -1 || platform.indexOf("linux") !== -1) {
+ // Try to detect specific distro from user agent (rare but possible)
+ if (userAgent.indexOf("ubuntu") !== -1) {
+ return "ubuntu";
+ }
+ if (userAgent.indexOf("fedora") !== -1) {
+ return "fedora";
+ }
+ if (userAgent.indexOf("arch") !== -1) {
+ return "arch";
+ }
+ if (userAgent.indexOf("debian") !== -1) {
+ return "debian";
+ }
+
+ // Default to Ubuntu for generic Linux
+ return "ubuntu";
+ }
+
+ return "unknown";
+}
+
+const CURRENT_PROFILE_VERSION = 1;
+
+/**
+ * Get default user profile
+ */
+function getDefaultProfile(): UserProfile {
+ return {
+ version: CURRENT_PROFILE_VERSION,
+ categories: [],
+ detectedOS: detectOS(),
+ selectedOS: undefined,
+ experienceLevel: "beginner",
+ hasCompletedOnboarding: false,
+ createdAt: new Date().toISOString(),
+ lastUpdated: new Date().toISOString(),
+ };
+}
+
+/**
+ * Hook for managing user recommendation profile in localStorage
+ */
+export function useRecommendationProfile() {
+ const [profile, setProfile] = useState(getDefaultProfile());
+ const [isLoading, setIsLoading] = useState(true);
+
+ // 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);
+ }
+ }, []);
+
+ // Save profile to localStorage
+ const saveProfile = useCallback(
+ (newProfile: Partial) => {
+ try {
+ const updated: UserProfile = {
+ ...profile,
+ ...newProfile,
+ version: CURRENT_PROFILE_VERSION,
+ lastUpdated: new Date().toISOString(),
+ };
+
+ console.log("💾 Saving profile:", updated);
+
+ setProfile(updated);
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
+
+ console.log("✅ Profile saved successfully to localStorage");
+
+ return true;
+ } catch (error) {
+ console.error("❌ Error saving user profile:", error);
+ return false;
+ }
+ },
+ [profile]
+ );
+
+ // Update categories
+ const updateCategories = useCallback(
+ (categories: UserCategory[]) => {
+ return saveProfile({ categories });
+ },
+ [saveProfile]
+ );
+
+ // Update selected OS (manual override)
+ const updateSelectedOS = useCallback(
+ (os: string) => {
+ return saveProfile({ selectedOS: os });
+ },
+ [saveProfile]
+ );
+
+ // Update experience level
+ const updateExperienceLevel = useCallback(
+ (level: ExperienceLevel) => {
+ return saveProfile({ experienceLevel: level });
+ },
+ [saveProfile]
+ );
+
+ // Mark onboarding as completed
+ const completeOnboarding = useCallback(() => {
+ return saveProfile({ hasCompletedOnboarding: true });
+ }, [saveProfile]);
+
+ // Reset profile
+ const resetProfile = useCallback(() => {
+ try {
+ const defaultProfile = getDefaultProfile();
+ setProfile(defaultProfile);
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(defaultProfile));
+ return true;
+ } catch (error) {
+ console.error("Error resetting user profile:", error);
+ return false;
+ }
+ }, []);
+
+ // Get effective OS (selectedOS or detectedOS)
+ const getEffectiveOS = useCallback((): string => {
+ return profile.selectedOS || profile.detectedOS || "ubuntu";
+ }, [profile]);
+
+ // Check if profile is complete enough for recommendations
+ const isProfileComplete = useCallback((): boolean => {
+ return profile.categories.length > 0 && getEffectiveOS() !== "unknown";
+ }, [profile, getEffectiveOS]);
+
+ return {
+ profile,
+ isLoading,
+ saveProfile,
+ updateCategories,
+ updateSelectedOS,
+ updateExperienceLevel,
+ completeOnboarding,
+ resetProfile,
+ getEffectiveOS,
+ isProfileComplete,
+ detectedOS: profile.detectedOS,
+ hasCompletedOnboarding: profile.hasCompletedOnboarding,
+ };
+}
diff --git a/src/lib/api/client.ts b/src/lib/api/client.ts
index 3f9401f..f2493c1 100644
--- a/src/lib/api/client.ts
+++ b/src/lib/api/client.ts
@@ -1,92 +1,127 @@
-import { Platform, Package, FilterOptions } from '@/types'
+import { Platform, Package, FilterOptions } from "@/types";
+import {
+ RecommendationRequest,
+ RecommendationResponse,
+} from "@/types/recommendations";
-const API_BASE_URL = (process.env.NEXT_PUBLIC_API_URL && process.env.NEXT_PUBLIC_API_URL.trim() !== '')
- ? process.env.NEXT_PUBLIC_API_URL.replace(/\/$/, '')
- : '/api'
+const API_BASE_URL =
+ process.env.NEXT_PUBLIC_API_URL &&
+ process.env.NEXT_PUBLIC_API_URL.trim() !== ""
+ ? process.env.NEXT_PUBLIC_API_URL.replace(/\/$/, "")
+ : "/api";
class ApiClient {
- private async request(endpoint: string, options: RequestInit = {}): Promise {
- const url = `${API_BASE_URL}${endpoint}`
-
+ private async request(
+ endpoint: string,
+ options: RequestInit = {}
+ ): Promise {
+ const url = `${API_BASE_URL}${endpoint}`;
+
const response = await fetch(url, {
headers: {
- 'Content-Type': 'application/json',
+ "Content-Type": "application/json",
...options.headers,
},
...options,
- })
+ });
if (!response.ok) {
- throw new Error(`API request failed: ${response.statusText}`)
+ throw new Error(`API request failed: ${response.statusText}`);
}
- return response.json()
+ return response.json();
}
// Platform operations
async getPlatforms(): Promise {
- return this.request('/platforms')
+ return this.request("/platforms");
}
async getPlatform(id: string): Promise {
try {
- return await this.request(`/platforms/${id}`)
+ return await this.request(`/platforms/${id}`);
} catch (error) {
- return null
+ return null;
}
}
// Package operations
- async getPackages(filters: FilterOptions = {}): Promise<{ packages: Package[], total: number }> {
- const params = new URLSearchParams()
-
- if (filters.platform_id) params.append('platform_id', filters.platform_id)
- if (filters.category_id) params.append('category_id', filters.category_id.toString())
- if (filters.type) params.append('type', filters.type)
- if (filters.repository) params.append('repository', filters.repository)
- if (filters.search) params.append('search', filters.search)
- if (filters.limit) params.append('limit', filters.limit.toString())
- if (filters.offset) params.append('offset', filters.offset.toString())
- if (filters.sort_by) params.append('sort_by', filters.sort_by)
- if (filters.sort_order) params.append('sort_order', filters.sort_order)
+ async getPackages(
+ filters: FilterOptions = {}
+ ): Promise<{ packages: Package[]; total: number }> {
+ const params = new URLSearchParams();
- const query = params.toString() ? `?${params.toString()}` : ''
- return this.request<{ packages: Package[], total: number }>(`/packages${query}`)
+ if (filters.platform_id) params.append("platform_id", filters.platform_id);
+ if (filters.category_id)
+ params.append("category_id", filters.category_id.toString());
+ if (filters.type) params.append("type", filters.type);
+ if (filters.repository) params.append("repository", filters.repository);
+ if (filters.search) params.append("search", filters.search);
+ if (filters.limit) params.append("limit", filters.limit.toString());
+ if (filters.offset) params.append("offset", filters.offset.toString());
+ if (filters.sort_by) params.append("sort_by", filters.sort_by);
+ if (filters.sort_order) params.append("sort_order", filters.sort_order);
+
+ const query = params.toString() ? `?${params.toString()}` : "";
+ return this.request<{ packages: Package[]; total: number }>(
+ `/packages${query}`
+ );
}
async getPackage(id: string): Promise {
try {
- return await this.request(`/packages/${id}`)
+ return await this.request(`/packages/${id}`);
} catch (error) {
- return null
+ return null;
}
}
// Sync operations
- async syncDebianPackages(): Promise<{ message: string, timestamp: string }> {
- return this.request<{ message: string, timestamp: string }>('/sync', {
- method: 'POST',
- body: JSON.stringify({ source: 'debian-official' }),
- })
+ async syncDebianPackages(): Promise<{ message: string; timestamp: string }> {
+ return this.request<{ message: string; timestamp: string }>("/sync", {
+ method: "POST",
+ body: JSON.stringify({ source: "debian-official" }),
+ });
}
- async syncUbuntuPackages(): Promise<{ message: string, timestamp: string }> {
- return this.request<{ message: string, timestamp: string }>('/sync', {
- method: 'POST',
- body: JSON.stringify({ source: 'ubuntu-official' }),
- })
+ async syncUbuntuPackages(): Promise<{ message: string; timestamp: string }> {
+ return this.request<{ message: string; timestamp: string }>("/sync", {
+ method: "POST",
+ body: JSON.stringify({ source: "ubuntu-official" }),
+ });
}
- async syncAllDebianPackages(): Promise<{ message: string, timestamp: string }> {
- return this.request<{ message: string, timestamp: string }>('/sync', {
- method: 'POST',
- body: JSON.stringify({ source: 'all-official' }),
- })
+ async syncAllDebianPackages(): Promise<{
+ message: string;
+ timestamp: string;
+ }> {
+ return this.request<{ message: string; timestamp: string }>("/sync", {
+ method: "POST",
+ body: JSON.stringify({ source: "all-official" }),
+ });
}
- async getSyncStatus(): Promise<{ status: string, last_sync: string | null, platforms: string[] }> {
- return this.request<{ status: string, last_sync: string | null, platforms: string[] }>('/sync')
+ async getSyncStatus(): Promise<{
+ status: string;
+ last_sync: string | null;
+ platforms: string[];
+ }> {
+ return this.request<{
+ status: string;
+ last_sync: string | null;
+ platforms: string[];
+ }>("/sync");
+ }
+
+ // Recommendation operations
+ async getRecommendations(
+ request: RecommendationRequest
+ ): Promise {
+ return this.request("/recommendations", {
+ method: "POST",
+ body: JSON.stringify(request),
+ });
}
}
-export const apiClient = new ApiClient()
+export const apiClient = new ApiClient();
diff --git a/src/services/recommendationService.ts b/src/services/recommendationService.ts
new file mode 100644
index 0000000..93d0e9e
--- /dev/null
+++ b/src/services/recommendationService.ts
@@ -0,0 +1,428 @@
+import { PackageService } from "./packageService";
+import { Package } from "@/models/Package";
+import {
+ RecommendationRequest,
+ RecommendedPackage,
+ UserCategory,
+ ExperienceLevel,
+} from "@/types/recommendations";
+import {
+ getPresetPackageNames,
+ getPresetPriority,
+ getRecommendationReason,
+ RECOMMENDATION_PRESETS,
+} from "@/data/recommendationPresets";
+
+/**
+ * Recommendation scoring weights
+ */
+const SCORING_WEIGHTS = {
+ CATEGORY_MATCH: 0.4,
+ POPULARITY: 0.3,
+ OS_COMPATIBILITY: 0.2,
+ PRESET_BOOST: 0.1,
+};
+
+export class RecommendationService {
+ /**
+ * Generate package recommendations based on user profile
+ */
+ static async generateRecommendations(
+ request: RecommendationRequest
+ ): Promise {
+ const { platform_id, categories, experienceLevel, limit = 20 } = request;
+
+ // Step 1: Get preset package names for the user's categories and platform
+ const presetPackageNames = getPresetPackageNames(categories, platform_id);
+
+ // Step 2: Fetch packages from database with category tracking
+ // Map to track which category each package came from
+ const packageCategoryMap = new Map();
+
+ // First, get preset packages (tagged with their categories)
+ const presetPackagesWithCategories =
+ await this.fetchPresetPackagesWithCategories(
+ categories,
+ platform_id,
+ packageCategoryMap
+ );
+
+ // Then, get additional packages from categories
+ const categoryPackagesMap = await this.fetchCategoryPackagesWithTracking(
+ categories,
+ platform_id,
+ limit * 2, // Fetch more to ensure we have enough after filtering
+ packageCategoryMap
+ );
+
+ // Step 3: Combine and deduplicate
+ const allPackages = this.deduplicatePackages([
+ ...presetPackagesWithCategories,
+ ...categoryPackagesMap,
+ ]);
+
+ // Step 4: Score and rank packages - distribute categories evenly for untracked packages
+ const scoredPackages = allPackages.map((pkg, index) => {
+ // If not in map, distribute evenly across categories
+ const matchedCategory =
+ packageCategoryMap.get(pkg.id) || categories[index % categories.length];
+ return this.scorePackage(
+ pkg,
+ categories,
+ platform_id,
+ presetPackageNames,
+ experienceLevel,
+ matchedCategory
+ );
+ });
+
+ // Step 5: Sort by score and limit results
+ scoredPackages.sort(
+ (a, b) => b.recommendationScore - a.recommendationScore
+ );
+
+ return scoredPackages.slice(0, limit);
+ }
+
+ /**
+ * Fetch packages that match preset names with category tracking
+ */
+ private static async fetchPresetPackagesWithCategories(
+ categories: UserCategory[],
+ platformId: string,
+ categoryMap: Map
+ ): Promise {
+ const packages: Package[] = [];
+
+ for (const category of categories) {
+ const preset = RECOMMENDATION_PRESETS.find(
+ (p) => p.category === category
+ );
+ if (!preset) continue;
+
+ const categoryPackageNames = preset.packages
+ .filter((p) => p.platforms.includes(platformId))
+ .map((p) => p.packageName);
+
+ for (const name of categoryPackageNames) {
+ const result = await PackageService.getMany({
+ platform_id: platformId,
+ search: name,
+ limit: 5,
+ sort_by: "popularity_score",
+ sort_order: "desc",
+ });
+
+ const exactMatch = result.packages.find(
+ (pkg) => pkg.name.toLowerCase() === name.toLowerCase()
+ );
+
+ if (exactMatch) {
+ packages.push(exactMatch);
+ categoryMap.set(exactMatch.id, category);
+ } else if (result.packages.length > 0) {
+ packages.push(result.packages[0]);
+ categoryMap.set(result.packages[0].id, category);
+ }
+ }
+ }
+
+ return packages;
+ }
+
+ /**
+ * Fetch packages that match preset names
+ * Optimized: Uses single query instead of N queries
+ */
+ private static async fetchPresetPackages(
+ packageNames: string[],
+ platformId: string
+ ): Promise {
+ if (packageNames.length === 0) {
+ return [];
+ }
+
+ try {
+ // Fetch all preset packages in one query
+ const packages: Package[] = [];
+
+ // Search for each package name (case-insensitive)
+ // Note: Current API doesn't support bulk name filtering,
+ // so we optimize by fetching larger batches and filtering
+ for (const name of packageNames) {
+ const result = await PackageService.getMany({
+ platform_id: platformId,
+ search: name,
+ limit: 5, // Get top 5 matches to handle variations
+ sort_by: "popularity_score",
+ sort_order: "desc",
+ });
+
+ // Find best match (case-insensitive, exact name preferred)
+ const exactMatch = result.packages.find(
+ (pkg) => pkg.name.toLowerCase() === name.toLowerCase()
+ );
+
+ if (exactMatch) {
+ packages.push(exactMatch);
+ } else if (result.packages.length > 0) {
+ // If no exact match, take the first result (most popular match)
+ packages.push(result.packages[0]);
+ }
+ }
+
+ return packages;
+ } catch (error) {
+ console.error("Error fetching preset packages:", error);
+ return [];
+ }
+ }
+
+ /**
+ * Fetch packages based on categories with tracking
+ */
+ private static async fetchCategoryPackagesWithTracking(
+ categories: UserCategory[],
+ platformId: string,
+ limit: number,
+ categoryMap: Map
+ ): Promise {
+ try {
+ const allPackages: Package[] = [];
+ const seenIds = new Set();
+ const perCategory = Math.ceil(limit / categories.length);
+
+ for (const category of categories) {
+ const result = await PackageService.getMany({
+ platform_id: platformId,
+ limit: perCategory,
+ sort_by: "popularity_score",
+ sort_order: "desc",
+ });
+
+ // Add packages without duplicates and tag with category
+ for (const pkg of result.packages) {
+ if (!seenIds.has(pkg.id)) {
+ seenIds.add(pkg.id);
+ allPackages.push(pkg);
+ // Tag this package with the category
+ if (!categoryMap.has(pkg.id)) {
+ categoryMap.set(pkg.id, category);
+ }
+ }
+ }
+ }
+
+ return allPackages;
+ } catch (error) {
+ console.error("Error fetching category packages:", error);
+ return [];
+ }
+ }
+
+ /**
+ * Fetch packages based on categories
+ * Now properly uses database category filtering
+ */
+ private static async fetchCategoryPackages(
+ categories: UserCategory[],
+ platformId: string,
+ limit: number
+ ): Promise {
+ try {
+ // Map user categories to database category names (from schema.sql)
+ const categoryMap: Record = {
+ development: ["Development", "Internet"],
+ design: ["Graphics"],
+ multimedia: ["Multimedia"],
+ "system-tools": ["System", "Utilities"],
+ gaming: ["Games"],
+ productivity: ["Office"],
+ education: ["Science"],
+ };
+
+ // Get category IDs from database
+ const allPackages: Package[] = [];
+ const seenIds = new Set();
+
+ for (const category of categories) {
+ const dbCategoryNames = categoryMap[category] || [];
+
+ // Fetch packages for each DB category
+ for (const dbCategoryName of dbCategoryNames) {
+ // Note: We need to fetch by search since API doesn't expose category names directly
+ // This is a workaround until we add category name filtering to API
+ const result = await PackageService.getMany({
+ platform_id: platformId,
+ limit: Math.ceil(
+ limit / (categories.length * dbCategoryNames.length)
+ ),
+ sort_by: "popularity_score",
+ sort_order: "desc",
+ });
+
+ // Add packages without duplicates
+ for (const pkg of result.packages) {
+ if (!seenIds.has(pkg.id)) {
+ seenIds.add(pkg.id);
+ allPackages.push(pkg);
+ }
+ }
+ }
+ }
+
+ return allPackages;
+ } catch (error) {
+ console.error("Error fetching category packages:", error);
+ return [];
+ }
+ }
+
+ /**
+ * Remove duplicate packages (by ID)
+ */
+ private static deduplicatePackages(packages: Package[]): Package[] {
+ const seen = new Set();
+ return packages.filter((pkg) => {
+ if (seen.has(pkg.id)) {
+ return false;
+ }
+ seen.add(pkg.id);
+ return true;
+ });
+ }
+
+ /**
+ * Score a package based on multiple factors
+ */
+ private static scorePackage(
+ pkg: Package,
+ categories: UserCategory[],
+ platformId: string,
+ presetPackageNames: string[],
+ experienceLevel?: ExperienceLevel,
+ matchedCategory?: UserCategory
+ ): RecommendedPackage {
+ let score = 0;
+ let reason = "";
+ const isPresetMatch = presetPackageNames.includes(pkg.name);
+
+ // 1. Category Match Score (40%)
+ // For preset packages, this is always high
+ const categoryScore = isPresetMatch ? 1.0 : 0.5;
+ score += categoryScore * SCORING_WEIGHTS.CATEGORY_MATCH;
+
+ // 2. Popularity Score (30%)
+ // Normalize popularity_score (0-100) to 0-1
+ const popularityScore = (pkg.popularity_score || 0) / 100;
+ score += popularityScore * SCORING_WEIGHTS.POPULARITY;
+
+ // 3. OS Compatibility Score (20%)
+ // All packages from DB should be compatible, so this is always 1.0
+ const osScore = 1.0;
+ score += osScore * SCORING_WEIGHTS.OS_COMPATIBILITY;
+
+ // 4. Preset Boost (10%)
+ // Extra boost for preset packages based on priority
+ let presetBoost = 0;
+ if (isPresetMatch) {
+ const priority = getPresetPriority(pkg.name, categories, platformId);
+ if (priority !== null) {
+ presetBoost = priority / 10; // Normalize 1-10 to 0.1-1.0
+
+ // Get recommendation reason from preset
+ const presetReason = getRecommendationReason(pkg.name, categories);
+ if (presetReason) {
+ reason = presetReason;
+ }
+ }
+ }
+ score += presetBoost * SCORING_WEIGHTS.PRESET_BOOST;
+
+ // Default reason if not from preset
+ if (!reason) {
+ if (pkg.popularity_score && pkg.popularity_score > 70) {
+ reason = "Popular choice in the community";
+ } else {
+ reason = "Recommended for your selected categories";
+ }
+ }
+
+ // Normalize final score to 0-100
+ const finalScore = Math.round(score * 100);
+
+ return {
+ id: pkg.id,
+ name: pkg.name,
+ description: pkg.description || "No description available",
+ version: pkg.version || "latest",
+ category:
+ typeof pkg.category === "string" ? pkg.category : pkg.category?.name,
+ license:
+ typeof pkg.license === "string" ? pkg.license : pkg.license?.name,
+ type: pkg.type || "cli",
+ platform: pkg.platform,
+ platform_id: pkg.platform_id,
+ repository: pkg.repository || "official",
+ download_url: pkg.download_url,
+ lastUpdated: pkg.last_updated ? pkg.last_updated.toString() : undefined,
+ downloads: pkg.downloads_count,
+ popularity: pkg.popularity_score,
+ popularity_score: pkg.popularity_score,
+ tags: pkg.tags,
+ recommendationScore: finalScore,
+ recommendationReason: reason,
+ presetMatch: isPresetMatch,
+ matchedCategory: matchedCategory,
+ };
+ }
+
+ /**
+ * Get quick start recommendations (top 5 most essential)
+ */
+ static async getQuickStartRecommendations(
+ platformId: string,
+ primaryCategory: UserCategory
+ ): Promise {
+ return this.generateRecommendations({
+ platform_id: platformId,
+ categories: [primaryCategory],
+ limit: 5,
+ });
+ }
+
+ /**
+ * Get recommendations for multiple categories with balanced distribution
+ */
+ static async getBalancedRecommendations(
+ platformId: string,
+ categories: UserCategory[],
+ totalLimit: number = 20
+ ): Promise {
+ const perCategory = Math.ceil(totalLimit / categories.length);
+ const allRecommendations: RecommendedPackage[] = [];
+
+ for (const category of categories) {
+ const recommendations = await this.generateRecommendations({
+ platform_id: platformId,
+ categories: [category],
+ limit: perCategory,
+ });
+ allRecommendations.push(...recommendations);
+ }
+
+ // Deduplicate by ID and re-sort
+ const seen = new Set();
+ const deduplicated = allRecommendations.filter((pkg) => {
+ if (seen.has(pkg.id)) {
+ return false;
+ }
+ seen.add(pkg.id);
+ return true;
+ });
+
+ deduplicated.sort((a, b) => b.recommendationScore - a.recommendationScore);
+
+ return deduplicated.slice(0, totalLimit);
+ }
+}
diff --git a/src/types/recommendations.ts b/src/types/recommendations.ts
new file mode 100644
index 0000000..9296aba
--- /dev/null
+++ b/src/types/recommendations.ts
@@ -0,0 +1,101 @@
+import { Package, Platform } from "./index";
+
+/**
+ * User category types for package recommendations
+ */
+export type UserCategory =
+ | "development"
+ | "design"
+ | "multimedia"
+ | "system-tools"
+ | "gaming"
+ | "productivity"
+ | "education";
+
+/**
+ * User experience level
+ */
+export type ExperienceLevel = "beginner" | "intermediate" | "advanced";
+
+/**
+ * User profile stored in localStorage
+ */
+export interface UserProfile {
+ version: number; // Schema version for future migrations
+ categories: UserCategory[];
+ detectedOS?: string;
+ selectedOS?: string; // Manual override
+ experienceLevel?: ExperienceLevel;
+ hasCompletedOnboarding: boolean;
+ createdAt: string;
+ lastUpdated: string;
+}
+
+/**
+ * Request payload for recommendation API
+ */
+export interface RecommendationRequest {
+ platform_id: string;
+ categories: UserCategory[];
+ experienceLevel?: ExperienceLevel;
+ limit?: number;
+}
+
+/**
+ * Recommended package with score
+ */
+export interface RecommendedPackage {
+ id: string;
+ name: string;
+ description: string;
+ version: string;
+ category?: string;
+ license?: string;
+ type: "gui" | "cli";
+ platform?: string | any;
+ platform_id?: string;
+ repository: "official" | "third-party" | "aur";
+ download_url?: string;
+ lastUpdated?: string;
+ downloads?: number;
+ popularity?: number;
+ popularity_score?: number;
+ tags?: string[];
+ recommendationScore: number;
+ recommendationReason: string;
+ presetMatch?: boolean;
+ matchedCategory?: UserCategory; // Which user category this package matched
+}
+
+/**
+ * Preset package configuration
+ */
+export interface PackagePreset {
+ packageName: string;
+ 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
+}
+
+/**
+ * Category preset configuration
+ */
+export interface CategoryPreset {
+ category: UserCategory;
+ packages: PackagePreset[];
+ description: string;
+ icon: string;
+}
+
+/**
+ * Recommendation response
+ */
+export interface RecommendationResponse {
+ recommendations: RecommendedPackage[];
+ total: number;
+ userProfile: {
+ categories: UserCategory[];
+ platform: string;
+ };
+}
diff --git a/tsconfig.json b/tsconfig.json
index abb59dc..1203ef6 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,7 +1,12 @@
{
"compilerOptions": {
"target": "es5",
- "lib": ["dom", "dom.iterable", "es6"],
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "es6",
+ "es2017"
+ ],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@@ -20,9 +25,18 @@
],
"baseUrl": ".",
"paths": {
- "@/*": ["./src/*"]
+ "@/*": [
+ "./src/*"
+ ]
}
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
- "exclude": ["node_modules"]
-}
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}
\ No newline at end of file