├── public ├── favicon.ico ├── assets │ └── images │ │ ├── 419.png │ │ ├── logo.png │ │ ├── favicon.ico │ │ ├── og-image.png │ │ ├── logo-icon.png │ │ ├── github-avatar.jpg │ │ └── sentry-wordmark-dark-400x119.png ├── robots.txt ├── index.php ├── js │ └── filament │ │ └── forms │ │ └── components │ │ ├── textarea.js │ │ ├── tags-input.js │ │ └── key-value.js ├── .htaccess ├── vendor │ └── feed │ │ └── style.css └── css │ └── app │ └── spatie-markdown-editor.css ├── database ├── .gitignore ├── settings │ ├── 2025_08_06_230502_create_general_settings.php │ ├── 2025_11_09_000000_add_extended_seo_settings.php │ ├── 2025_08_08_215627_create_social_accounts_settings.php │ └── 2025_08_06_233023_create_seo_settings.php ├── seeders │ └── DatabaseSeeder.php ├── migrations │ ├── 2022_12_14_083707_create_settings_table.php │ ├── 2025_03_06_003846_alter_indexes_table.php │ ├── 2025_07_15_192921_add_is_featured_to_packages_table.php │ ├── 2025_03_05_181441_add_is_admin_to_users_table.php │ ├── 2025_07_29_210517_add_notification_sent_to_package_submissions_table.php │ ├── 2025_08_01_183554_create_newsletter_subscribers_table.php │ ├── 2025_03_08_231823_create_index_package_table.php │ ├── 2025_02_12_191127_create_admins_table.php │ ├── 2025_07_20_153252_add_user_id_to_packages_table.php │ ├── 2025_08_01_231351_alter_user_agent_length_in_blog_post_views_table.php │ ├── 2025_03_09_021616_make_index_id_nullable_in_packages_table.php │ ├── 2025_02_18_032836_create_category_package_table.php │ ├── 2025_12_08_222333_create_feed_post_bookmarks_table.php │ ├── 2025_03_15_222014_create_blog_post_category_table.php │ ├── 2025_07_27_200031_create_package_submissions_table.php │ ├── 2025_12_08_191651_create_feed_comment_user_table.php │ ├── 0001_01_01_000001_create_cache_table.php │ ├── 2025_02_28_194503_create_personal_access_tokens_table.php │ ├── 2025_12_08_191403_create_feed_post_user_table.php │ ├── 2025_02_14_202654_create_indexes_table.php │ ├── 2025_12_08_222318_create_feed_post_votes_table.php │ ├── 2025_02_18_001104_create_categories_table.php │ ├── 2025_07_20_151912_add_username_to_users_table.php │ ├── 2025_02_14_202331_create_media_table.php │ ├── 2025_12_08_191402_create_feed_comments_table.php │ ├── 2025_04_04_140517_create_blog_post_views_table.php │ ├── 2025_07_19_151100_add_social_auth_fields_to_users_table.php │ ├── 2025_03_15_221709_create_blog_posts_table.php │ ├── 2025_12_08_191329_create_feed_sources_table.php │ └── 2025_02_18_030230_create_packages_table.php └── factories │ └── UserFactory.php ├── resources ├── js │ ├── types │ │ ├── globals.d.ts │ │ ├── vite-env.d.ts │ │ └── global.d.ts │ ├── bootstrap.ts │ ├── hooks │ │ ├── use-slugify.ts │ │ ├── use-click-tracker.ts │ │ ├── use-forwarded-ref.ts │ │ ├── use-mobile.tsx │ │ └── use-image-upload.ts │ ├── components │ │ ├── ui │ │ │ ├── collapsible.tsx │ │ │ ├── skeleton.tsx │ │ │ ├── textarea.tsx │ │ │ ├── input.tsx │ │ │ ├── label.tsx │ │ │ ├── separator.tsx │ │ │ ├── floating-element.tsx │ │ │ ├── tooltip.tsx │ │ │ ├── switch.tsx │ │ │ ├── badge.tsx │ │ │ └── popover.tsx │ │ ├── shared │ │ │ └── AppHead.tsx │ │ ├── mode-toggle.tsx │ │ ├── input-error.tsx │ │ ├── input-label.tsx │ │ ├── checkbox.tsx │ │ ├── single-select.tsx │ │ ├── danger-button.tsx │ │ ├── multi-select.tsx │ │ ├── primary-button.tsx │ │ ├── nav-link.tsx │ │ ├── secondary-button.tsx │ │ ├── text-input.tsx │ │ └── responsive-nav-link.tsx │ ├── lib │ │ └── utils.ts │ ├── Layouts │ │ └── GuestLayout.tsx │ ├── ssr.tsx │ ├── app.tsx │ └── Pages │ │ └── Admin │ │ └── Profile │ │ └── Edit.tsx ├── views │ ├── vendor │ │ ├── mail │ │ │ ├── text │ │ │ │ ├── footer.blade.php │ │ │ │ ├── panel.blade.php │ │ │ │ ├── subcopy.blade.php │ │ │ │ ├── table.blade.php │ │ │ │ ├── button.blade.php │ │ │ │ ├── header.blade.php │ │ │ │ ├── layout.blade.php │ │ │ │ └── message.blade.php │ │ │ └── html │ │ │ │ ├── table.blade.php │ │ │ │ ├── subcopy.blade.php │ │ │ │ ├── header.blade.php │ │ │ │ ├── footer.blade.php │ │ │ │ ├── panel.blade.php │ │ │ │ ├── message.blade.php │ │ │ │ └── button.blade.php │ │ └── feed │ │ │ └── links.blade.php │ ├── filament │ │ ├── pages │ │ │ └── analytics.blade.php │ │ └── widgets │ │ │ └── blog-analytics-stats.blade.php │ └── errors │ │ └── 419.blade.php └── css │ └── sonner-overrides.css ├── bootstrap ├── cache │ └── .gitignore └── providers.php ├── storage ├── logs │ └── .gitignore ├── app │ ├── private │ │ └── .gitignore │ ├── public │ │ └── .gitignore │ └── .gitignore ├── debugbar │ └── .gitignore ├── framework │ ├── testing │ │ └── .gitignore │ ├── views │ │ └── .gitignore │ ├── cache │ │ ├── data │ │ │ └── .gitignore │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ └── .gitignore └── model_status_installed.lock ├── postcss.config.js ├── app ├── Http │ ├── Controllers │ │ ├── Controller.php │ │ ├── Auth │ │ │ ├── Social │ │ │ │ ├── GithubRedirectController.php │ │ │ │ └── GithubCallbackController.php │ │ │ ├── EmailVerificationPromptController.php │ │ │ ├── EmailVerificationNotificationController.php │ │ │ ├── PasswordController.php │ │ │ ├── VerifyEmailController.php │ │ │ ├── PasswordResetLinkController.php │ │ │ └── ConfirmablePasswordController.php │ │ ├── OgImageController.php │ │ ├── Api │ │ │ ├── SearchController.php │ │ │ ├── FeedController.php │ │ │ └── BookmarkController.php │ │ ├── Admin │ │ │ ├── DashboardController.php │ │ │ ├── GetPackageRepoDataController.php │ │ │ ├── Auth │ │ │ │ ├── PasswordController.php │ │ │ │ ├── ForgotPasswordController.php │ │ │ │ ├── LoginController.php │ │ │ │ └── ResetPasswordController.php │ │ │ ├── ToggleStatusController.php │ │ │ └── ProfileController.php │ │ ├── NewsletterSubscriptionController.php │ │ ├── Application │ │ │ └── Feed │ │ │ │ ├── TogglePostBookmarkController.php │ │ │ │ ├── FeedController.php │ │ │ │ ├── TogglePostVoteController.php │ │ │ │ ├── ToggleCommentVoteController.php │ │ │ │ ├── BookmarkController.php │ │ │ │ ├── FeedPostController.php │ │ │ │ └── FeedCommentController.php │ │ ├── UserController.php │ │ ├── Profile │ │ │ ├── ProfileSecurityController.php │ │ │ └── ProfileInformationController.php │ │ └── BlogController.php │ ├── Requests │ │ ├── Admin │ │ │ ├── GetPackageRepoDataRequest.php │ │ │ ├── ProfileUpdateRequest.php │ │ │ ├── CreateIndexRequest.php │ │ │ └── UpdateIndexRequest.php │ │ ├── SecurityUpdateRequest.php │ │ ├── Application │ │ │ └── Feed │ │ │ │ ├── ToggleVoteRequest.php │ │ │ │ └── CreateCommentRequest.php │ │ ├── ProfileUpdateRequest.php │ │ ├── CreateNewsletterSubscriptionRequest.php │ │ └── CreatePackageSubmissionRequest.php │ ├── Resources │ │ ├── Application │ │ │ └── Feed │ │ │ │ ├── FeedSourceResource.php │ │ │ │ └── FeedCommentResource.php │ │ ├── IndexResource.php │ │ ├── CategoryResource.php │ │ ├── UserResource.php │ │ ├── Admin │ │ │ └── IndexResource.php │ │ ├── BlogPostResource.php │ │ ├── PackageSubmissionResource.php │ │ └── PackageResource.php │ └── Middleware │ │ └── AdminAuthMiddleware.php ├── Enums │ ├── SocialProvider.php │ └── ReviewStatus.php ├── Traits │ └── HasStatus.php ├── Support │ └── AdminDetector.php ├── Settings │ ├── GeneralSettings.php │ ├── SocialAccountsSettings.php │ └── SeoSettings.php ├── View │ └── Components │ │ └── vendor │ │ └── feed │ │ └── FeedLinks.php ├── Queries │ ├── MostReadPostsThisWeekQuery.php │ ├── AvatarUsersMapQuery.php │ ├── LatestPublishedPostsQuery.php │ └── PackagesOrderedByFeaturedQuery.php ├── Filament │ ├── Resources │ │ ├── IndexResource │ │ │ └── Pages │ │ │ │ ├── ViewIndex.php │ │ │ │ ├── CreateIndex.php │ │ │ │ ├── EditIndex.php │ │ │ │ └── ListIndices.php │ │ ├── UserResource │ │ │ └── Pages │ │ │ │ ├── CreateUser.php │ │ │ │ ├── EditUser.php │ │ │ │ ├── ViewUser.php │ │ │ │ └── ListUsers.php │ │ ├── BlogPostResource │ │ │ └── Pages │ │ │ │ ├── CreateBlogPost.php │ │ │ │ ├── EditBlogPost.php │ │ │ │ └── ListBlogPosts.php │ │ ├── FeedSourceResource │ │ │ └── Pages │ │ │ │ ├── CreateFeedSource.php │ │ │ │ ├── EditFeedSource.php │ │ │ │ └── ListFeedSources.php │ │ ├── CategoryResource │ │ │ └── Pages │ │ │ │ └── ViewPackageCategory.php │ │ ├── BlogPostCategoryResource │ │ │ └── Pages │ │ │ │ ├── ViewBlogPostCategory.php │ │ │ │ ├── CreateBlogPostCategory.php │ │ │ │ ├── EditBlogPostCategory.php │ │ │ │ └── ListBlogPostCategories.php │ │ ├── PackageCategoryResource │ │ │ └── Pages │ │ │ │ ├── CreatePackageCategory.php │ │ │ │ ├── EditPackageCategory.php │ │ │ │ └── ListPackageCategories.php │ │ ├── NewsletterSubscriberResource │ │ │ └── Pages │ │ │ │ ├── ViewNewsletterSubscriber.php │ │ │ │ └── ListNewsletterSubscribers.php │ │ ├── PackageSubmissionResource │ │ │ └── Pages │ │ │ │ └── ListPackageSubmissions.php │ │ └── PackageResource │ │ │ └── Pages │ │ │ └── CreatePackage.php │ ├── Widgets │ │ └── StatsOverview.php │ └── Pages │ │ ├── Settings │ │ └── GeneralSettings.php │ │ ├── Analytics.php │ │ └── ManageSettings.php ├── Models │ ├── BlogPostView.php │ ├── Admin.php │ ├── NewsletterSubscriber.php │ ├── PackageSubmission.php │ ├── FeedPostBookmark.php │ ├── FeedPostVote.php │ └── Scopes │ │ └── PublishedScope.php ├── ModelFilters │ ├── FeedPostFilter.php │ └── PackageFilter.php ├── Actions │ ├── CreatePackageSubmissionAction.php │ ├── CreateNewsletterSubscriptionAction.php │ ├── Feed │ │ └── TogglePostBookmarkAction.php │ └── SendPasswordResetLinkAction.php ├── Services │ ├── MixpanelService.php │ └── UserService.php ├── Rules │ └── AdminCurrentPassword.php ├── Console │ └── Commands │ │ ├── PublishScheduledPosts.php │ │ ├── RelateIndexesToPackages.php │ │ └── FixSequences.php ├── Providers │ └── HorizonServiceProvider.php ├── Jobs │ ├── GenerateOgImageForPackageJob.php │ └── RecordBlogPostViewsJob.php └── Notifications │ └── NewsletterWelcomeNotification.php ├── pint.json ├── tests ├── TestCase.php └── Feature │ └── Auth │ ├── RegistrationTest.php │ └── PasswordConfirmationTest.php ├── .gitattributes ├── routes ├── console.php └── api.php ├── config ├── filament-page-with-sidebar.php └── services.php ├── .editorconfig ├── vite.config.js ├── artisan ├── .gitignore ├── components.json ├── lang └── en │ ├── pagination.php │ ├── auth.php │ └── passwords.php ├── tsconfig.json ├── .prettierignore ├── .prettierrc ├── LICENSE └── phpunit.xml /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite* 2 | -------------------------------------------------------------------------------- /resources/js/types/globals.d.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/app/private/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/debugbar/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/model_status_installed.lock: -------------------------------------------------------------------------------- 1 | 2025-02-17 20:24:34 -------------------------------------------------------------------------------- /storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/footer.blade.php: -------------------------------------------------------------------------------- 1 | {{ $slot }} 2 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/panel.blade.php: -------------------------------------------------------------------------------- 1 | {{ $slot }} 2 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/subcopy.blade.php: -------------------------------------------------------------------------------- 1 | {{ $slot }} 2 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/table.blade.php: -------------------------------------------------------------------------------- 1 | {{ $slot }} 2 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !private/ 3 | !public/ 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /resources/js/types/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/button.blade.php: -------------------------------------------------------------------------------- 1 | {{ $slot }}: {{ $url }} 2 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/header.blade.php: -------------------------------------------------------------------------------- 1 | {{ $slot }}: {{ $url }} 2 | -------------------------------------------------------------------------------- /public/assets/images/419.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheLaravelHub/laravel-hub/HEAD/public/assets/images/419.png -------------------------------------------------------------------------------- /public/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheLaravelHub/laravel-hub/HEAD/public/assets/images/logo.png -------------------------------------------------------------------------------- /public/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheLaravelHub/laravel-hub/HEAD/public/assets/images/favicon.ico -------------------------------------------------------------------------------- /public/assets/images/og-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheLaravelHub/laravel-hub/HEAD/public/assets/images/og-image.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/assets/images/logo-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheLaravelHub/laravel-hub/HEAD/public/assets/images/logo-icon.png -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/table.blade.php: -------------------------------------------------------------------------------- 1 |
2 | {{ Illuminate\Mail\Markdown::parse($slot) }} 3 |
4 | -------------------------------------------------------------------------------- /public/assets/images/github-avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheLaravelHub/laravel-hub/HEAD/public/assets/images/github-avatar.jpg -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ Illuminate\Mail\Markdown::parse($slot) }} 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/layout.blade.php: -------------------------------------------------------------------------------- 1 | {!! strip_tags($header ?? '') !!} 2 | 3 | {!! strip_tags($slot) !!} 4 | @isset($subcopy) 5 | 6 | {!! strip_tags($subcopy) !!} 7 | @endisset 8 | 9 | {!! strip_tags($footer ?? '') !!} 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.blade.php diff=html 4 | *.css diff=css 5 | *.html diff=html 6 | *.md diff=markdown 7 | *.php diff=php 8 | 9 | /.github export-ignore 10 | CHANGELOG.md export-ignore 11 | .styleci.yml export-ignore 12 | -------------------------------------------------------------------------------- /app/Traits/HasStatus.php: -------------------------------------------------------------------------------- 1 | attributes['status'] = $value ? 'active' : 'inactive'; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 8 | })->purpose('Display an inspiring quote'); 9 | -------------------------------------------------------------------------------- /bootstrap/providers.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | @foreach ($this->getVisibleWidgets() as $widget) 4 | @livewire($widget) 5 | @endforeach 6 |
7 | 8 | -------------------------------------------------------------------------------- /resources/views/vendor/feed/links.blade.php: -------------------------------------------------------------------------------- 1 | @foreach($feeds as $name => $feed) 2 | 3 | @endforeach 4 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/header.blade.php: -------------------------------------------------------------------------------- 1 | @props(['url']) 2 | 3 | 4 | 5 | YourAppName 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/Support/AdminDetector.php: -------------------------------------------------------------------------------- 1 | is_admin ?? false); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /config/filament-page-with-sidebar.php: -------------------------------------------------------------------------------- 1 | [ 8 | '2xl' => '3', 9 | 'xl' => '3', 10 | 'lg' => '3', 11 | 'md' => '3', 12 | 'sm' => '12', 13 | ], 14 | ]; 15 | -------------------------------------------------------------------------------- /resources/js/hooks/use-slugify.ts: -------------------------------------------------------------------------------- 1 | const useSlugify = () => { 2 | const slugify = (str: string) => { 3 | return str 4 | .toLowerCase() 5 | .replace(/ /g, '-') 6 | .replace(/[^\w-]+/g, '') 7 | } 8 | return { slugify } 9 | } 10 | 11 | export default useSlugify 12 | -------------------------------------------------------------------------------- /app/Settings/GeneralSettings.php: -------------------------------------------------------------------------------- 1 | migrator->add('general.site_name', env('APP_NAME')); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/footer.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/Queries/MostReadPostsThisWeekQuery.php: -------------------------------------------------------------------------------- 1 | { 5 | return useCallback(() => { 6 | Mixpanel.track(type, options) 7 | }, [type, options]) 8 | } 9 | 10 | export default useClickTracker 11 | -------------------------------------------------------------------------------- /app/Models/BlogPostView.php: -------------------------------------------------------------------------------- 1 | belongsTo(BlogPost::class); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/Filament/Resources/BlogPostResource/Pages/CreateBlogPost.php: -------------------------------------------------------------------------------- 1 | query->whereAny(['title', 'excerpt'], 'ILIKE', "%{$query}%"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /resources/js/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/lib/utils' 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | -------------------------------------------------------------------------------- /resources/js/components/shared/AppHead.tsx: -------------------------------------------------------------------------------- 1 | import { Head } from '@inertiajs/react' 2 | 3 | export default function AppHead({ 4 | title, 5 | children, 6 | }: { 7 | title?: string 8 | children: React.ReactNode 9 | }) { 10 | return ( 11 | 12 | {title ? title : 'Laravel Hub'} 13 | {children} 14 | 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import laravel from 'laravel-vite-plugin' 3 | import react from '@vitejs/plugin-react' 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | laravel({ 8 | input: 'resources/js/app.tsx', 9 | ssr: 'resources/js/ssr.tsx', 10 | refresh: true, 11 | }), 12 | react(), 13 | ], 14 | }) 15 | -------------------------------------------------------------------------------- /app/Filament/Resources/BlogPostCategoryResource/Pages/ViewBlogPostCategory.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 |
7 | {{ Illuminate\Mail\Markdown::parse($slot) }} 8 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/Filament/Resources/BlogPostCategoryResource/Pages/CreateBlogPostCategory.php: -------------------------------------------------------------------------------- 1 | handleCommand(new ArgvInput); 14 | 15 | exit($status); 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.phpunit.cache 2 | /bootstrap/ssr 3 | /node_modules 4 | /public/build 5 | /public/hot 6 | /public/storage 7 | /storage/*.key 8 | /storage/pail 9 | /vendor 10 | .env 11 | .env.backup 12 | .env.production 13 | .phpactor.json 14 | .phpunit.result.cache 15 | Homestead.json 16 | Homestead.yaml 17 | npm-debug.log 18 | yarn-error.log 19 | /auth.json 20 | /.fleet 21 | /.idea 22 | /.nova 23 | /.vscode 24 | /.zed 25 | .env.testing 26 | -------------------------------------------------------------------------------- /app/Filament/Resources/NewsletterSubscriberResource/Pages/ViewNewsletterSubscriber.php: -------------------------------------------------------------------------------- 1 | redirect(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/Http/Controllers/OgImageController.php: -------------------------------------------------------------------------------- 1 | 11 | 12 | Light mode 13 | 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /resources/js/components/input-error.tsx: -------------------------------------------------------------------------------- 1 | import { HTMLAttributes } from 'react' 2 | 3 | export default function InputError({ 4 | message, 5 | className = '', 6 | ...props 7 | }: HTMLAttributes & { message?: string }) { 8 | return message ? ( 9 |

13 | {message} 14 |

15 | ) : null 16 | } 17 | -------------------------------------------------------------------------------- /app/Models/Admin.php: -------------------------------------------------------------------------------- 1 | 'hashed', 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /resources/js/hooks/use-forwarded-ref.ts: -------------------------------------------------------------------------------- 1 | import type React from 'react' 2 | import { useEffect, useRef } from 'react' 3 | 4 | export function useForwardedRef(ref: React.ForwardedRef) { 5 | const innerRef = useRef(null) 6 | 7 | useEffect(() => { 8 | if (!ref) return 9 | if (typeof ref === 'function') { 10 | ref(innerRef.current) 11 | } else { 12 | ref.current = innerRef.current 13 | } 14 | }) 15 | 16 | return innerRef 17 | } 18 | -------------------------------------------------------------------------------- /app/ModelFilters/PackageFilter.php: -------------------------------------------------------------------------------- 1 | whereRaw('LOWER(language) = ?', [strtolower($lang)]); 12 | } 13 | 14 | public function isFeatured(bool $isFeatured = false) 15 | { 16 | if ($isFeatured) { 17 | return $this->orderByDesc('is_featured'); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Filament/Resources/UserResource/Pages/EditUser.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | protected $casts = [ 20 | 'is_active' => 'boolean', 21 | ]; 22 | } 23 | -------------------------------------------------------------------------------- /app/Filament/Resources/BlogPostResource/Pages/EditBlogPost.php: -------------------------------------------------------------------------------- 1 | handleRequest(Request::capture()); 18 | -------------------------------------------------------------------------------- /app/Filament/Resources/BlogPostResource/Pages/ListBlogPosts.php: -------------------------------------------------------------------------------- 1 | whereNotNull('avatar') 13 | ->limit(6) 14 | ->get(['id', 'name', 'avatar']) 15 | ->keyBy('id') 16 | ->map(fn ($u) => [ 17 | 'name' => $u->name, 18 | 'avatar' => $u->avatar, 19 | ]) 20 | ->toArray(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Filament/Resources/FeedSourceResource/Pages/ListFeedSources.php: -------------------------------------------------------------------------------- 1 | published() 14 | ->select(['id', 'title', 'sub_title', 'slug', 'published_at', 'meta_description']) 15 | ->with('categories:id,name,slug') 16 | ->latest('published_at') 17 | ->limit(6) 18 | ->get(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /resources/js/components/input-label.tsx: -------------------------------------------------------------------------------- 1 | import { LabelHTMLAttributes } from 'react' 2 | 3 | export default function InputLabel({ 4 | value, 5 | className = '', 6 | children, 7 | ...props 8 | }: LabelHTMLAttributes & { value?: string }) { 9 | return ( 10 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /app/Filament/Resources/BlogPostCategoryResource/Pages/EditBlogPostCategory.php: -------------------------------------------------------------------------------- 1 | select(['id', 'name', 'slug', 'description', 'stars', 'owner', 'owner_avatar']) 14 | ->filter(['is_featured' => true]) 15 | ->with('categories:id,name,slug') 16 | ->orderByDesc('stars') 17 | ->limit(6) 18 | ->get(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Filament/Resources/NewsletterSubscriberResource/Pages/ListNewsletterSubscribers.php: -------------------------------------------------------------------------------- 1 | input('term')) ?? '') 17 | ->get(); 18 | 19 | $packages->load('categories'); 20 | 21 | return response()->json($packages); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Models/PackageSubmission.php: -------------------------------------------------------------------------------- 1 | 'boolean', 15 | ]; 16 | 17 | /** 18 | * Relationship: PackageSubmission belongs to a User 19 | */ 20 | public function user(): BelongsTo 21 | { 22 | return $this->belongsTo(User::class); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "resources/css/app.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } 22 | -------------------------------------------------------------------------------- /resources/js/components/checkbox.tsx: -------------------------------------------------------------------------------- 1 | import { InputHTMLAttributes } from 'react' 2 | 3 | export default function Checkbox({ 4 | className = '', 5 | ...props 6 | }: InputHTMLAttributes) { 7 | return ( 8 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | name('search') 10 | ->middleware('throttle:60,1'); 11 | 12 | Route::get('feed', [FeedController::class, 'index']) 13 | ->name('api.feed') 14 | ->middleware(['web', 'auth']); 15 | 16 | Route::get('bookmarks', [BookmarkController::class, 'index']) 17 | ->name('api.bookmarks') 18 | ->middleware(['web', 'auth']); 19 | -------------------------------------------------------------------------------- /app/Http/Requests/Admin/GetPackageRepoDataRequest.php: -------------------------------------------------------------------------------- 1 | |string> 13 | */ 14 | public function rules(): array 15 | { 16 | return [ 17 | 'repository_url' => ['required', 'url', 'regex:/github\.com\/([^\/]+)\/([^\/]+)/'], 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lang/en/pagination.php: -------------------------------------------------------------------------------- 1 | '« Previous', 17 | 'next' => 'Next »', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /app/Http/Resources/Application/Feed/FeedSourceResource.php: -------------------------------------------------------------------------------- 1 | $this->id, 16 | 'name' => $this->name, 17 | 'slug' => $this->slug, 18 | 'logo' => $this->logo_url, 19 | 'type' => $this->type, 20 | ]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Models/FeedPostBookmark.php: -------------------------------------------------------------------------------- 1 | belongsTo(FeedPost::class); 20 | } 21 | 22 | public function user(): BelongsTo 23 | { 24 | return $this->belongsTo(User::class); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /database/settings/2025_11_09_000000_add_extended_seo_settings.php: -------------------------------------------------------------------------------- 1 | migrator->add('seo.og_image', null); 10 | $this->migrator->add('seo.og_site_name', 'Laravel Hub'); 11 | $this->migrator->add('seo.og_locale', 'en_US'); 12 | $this->migrator->add('seo.twitter_card', 'summary_large_image'); 13 | $this->migrator->add('seo.twitter_site', '@thelaravelhub'); 14 | $this->migrator->add('seo.twitter_image', null); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /app/Actions/CreatePackageSubmissionAction.php: -------------------------------------------------------------------------------- 1 | $data 15 | */ 16 | public function handle(User $user, array $data): PackageSubmission 17 | { 18 | return $user->packageSubmissions()->create([ 19 | 'repository_url' => $data['repository_url'], 20 | 'status' => ReviewStatus::PENDING, 21 | ]); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Models/FeedPostVote.php: -------------------------------------------------------------------------------- 1 | belongsTo(FeedPost::class); 21 | } 22 | 23 | public function user(): BelongsTo 24 | { 25 | return $this->belongsTo(User::class); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /database/settings/2025_08_08_215627_create_social_accounts_settings.php: -------------------------------------------------------------------------------- 1 | migrator->add('social-accounts.github', null); 10 | $this->migrator->add('social-accounts.x', null); 11 | $this->migrator->add('social-accounts.facebook', null); 12 | $this->migrator->add('social-accounts.telegram', null); 13 | $this->migrator->add('social-accounts.bluesky', null); 14 | $this->migrator->add('social-accounts.linkedin', null); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/message.blade.php: -------------------------------------------------------------------------------- 1 | 2 | {{-- Header --}} 3 | 4 | 5 | {{ config('app.name') }} 6 | 7 | 8 | 9 | {{-- Body --}} 10 | {!! $slot !!} 11 | 12 | {{-- Subcopy --}} 13 | @isset($subcopy) 14 | 15 | 16 | {!! $subcopy !!} 17 | 18 | 19 | @endisset 20 | 21 | {{-- Footer --}} 22 | 23 | 24 | © {{ date('Y') }} {{ config('app.name') }}. {{ __('All rights reserved.') }} 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | create(); 17 | 18 | User::factory()->create([ 19 | 'name' => 'Admin', 20 | 'email' => 'admin@admin.dev', 21 | 'password' => bcrypt('password'), 22 | 'is_admin' => true, 23 | ]); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/DashboardController.php: -------------------------------------------------------------------------------- 1 | active()->count(); 15 | $inActivePackagesCount = Package::query()->inActive()->count(); 16 | 17 | return Inertia::render('Admin/User/Index', 18 | compact('packagesCount', 'activePackagesCount', 'inActivePackagesCount')); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/textarea.js: -------------------------------------------------------------------------------- 1 | function r({initialHeight:t,shouldAutosize:i,state:s}){return{state:s,wrapperEl:null,init:function(){this.wrapperEl=this.$el.parentNode,this.setInitialHeight(),i?this.$watch("state",()=>{this.resize()}):this.setUpResizeObserver()},setInitialHeight:function(){this.$el.scrollHeight<=0||(this.wrapperEl.style.height=t+"rem")},resize:function(){if(this.setInitialHeight(),this.$el.scrollHeight<=0)return;let e=this.$el.scrollHeight+"px";this.wrapperEl.style.height!==e&&(this.wrapperEl.style.height=e)},setUpResizeObserver:function(){new ResizeObserver(()=>{this.wrapperEl.style.height=this.$el.style.height}).observe(this.$el)}}}export{r as default}; 2 | -------------------------------------------------------------------------------- /app/Http/Controllers/NewsletterSubscriptionController.php: -------------------------------------------------------------------------------- 1 | handle($request->validated()); 15 | 16 | return Redirect::back()->with('success', 'Thanks for subscribing!'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Http/Requests/SecurityUpdateRequest.php: -------------------------------------------------------------------------------- 1 | |string> 14 | */ 15 | public function rules(): array 16 | { 17 | return [ 18 | 'current_password' => ['required', 'current_password'], 19 | 'password' => ['required', Password::defaults(), 'confirmed'], 20 | ]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Services/MixpanelService.php: -------------------------------------------------------------------------------- 1 | mixpanel = Mixpanel::getInstance(env('MIXPANEL_TOKEN')); 14 | } 15 | 16 | public function trackEvent(string $eventName, array $properties = []): void 17 | { 18 | $this->mixpanel->track($eventName, $properties); 19 | } 20 | 21 | public function identifyUser(string $distinctId, array $properties = []): void 22 | { 23 | $this->mixpanel->identify($distinctId); 24 | $this->mixpanel->people->set($distinctId, $properties); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/button.blade.php: -------------------------------------------------------------------------------- 1 | @props([ 2 | 'url', 3 | 'color' => 'primary', 4 | 'align' => 'center', 5 | ]) 6 | 7 | 8 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/Http/Middleware/AdminAuthMiddleware.php: -------------------------------------------------------------------------------- 1 | guard('admin')->check()) { 19 | return redirect()->route('admin.login')->with('error', 'Please log in as an admin.'); 20 | } 21 | 22 | return $next($request); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /database/migrations/2022_12_14_083707_create_settings_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | 14 | $table->string('group'); 15 | $table->string('name'); 16 | $table->boolean('locked')->default(false); 17 | $table->json('payload'); 18 | 19 | $table->timestamps(); 20 | 21 | $table->unique(['group', 'name']); 22 | }); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /resources/js/hooks/use-mobile.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | const MOBILE_BREAKPOINT = 768 4 | 5 | export function useIsMobile() { 6 | const [isMobile, setIsMobile] = React.useState( 7 | undefined, 8 | ) 9 | 10 | React.useEffect(() => { 11 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) 12 | const onChange = () => { 13 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 14 | } 15 | mql.addEventListener('change', onChange) 16 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 17 | return () => mql.removeEventListener('change', onChange) 18 | }, []) 19 | 20 | return !!isMobile 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "module": "ESNext", 5 | "moduleResolution": "bundler", 6 | "jsx": "react-jsx", 7 | "strict": true, 8 | "isolatedModules": true, 9 | "target": "ESNext", 10 | "esModuleInterop": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "skipLibCheck": true, 13 | "noEmit": true, 14 | "paths": { 15 | "@/*": ["./resources/js/*"], 16 | "ziggy-js": ["./vendor/tightenco/ziggy"] 17 | } 18 | }, 19 | "include": [ 20 | "resources/js/**/*.ts", 21 | "resources/js/**/*.tsx", 22 | "resources/js/**/*.d.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /app/Actions/CreateNewsletterSubscriptionAction.php: -------------------------------------------------------------------------------- 1 | $data 14 | */ 15 | public function handle(array $data): NewsletterSubscriber 16 | { 17 | $subscriber = NewsletterSubscriber::create([ 18 | 'email' => $data['email'], 19 | 'is_active' => true, 20 | ]); 21 | $subscriber->notify(new NewsletterWelcomeNotification); 22 | 23 | return $subscriber; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Services/UserService.php: -------------------------------------------------------------------------------- 1 | lower() 14 | ->replaceMatches('/[^a-z0-9]+/', '.') 15 | ->trim('.'); 16 | 17 | $username = $base; 18 | $i = 1; 19 | 20 | while ( 21 | DB::table('users') 22 | ->where('username', $username) 23 | ->exists() 24 | ) { 25 | $username = "{$base}{$i}"; 26 | $i++; 27 | } 28 | 29 | return $username; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lang/en/auth.php: -------------------------------------------------------------------------------- 1 | 'These credentials do not match our records.', 17 | 'password' => 'The provided password is incorrect.', 18 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 19 | 20 | ]; 21 | -------------------------------------------------------------------------------- /app/Models/Scopes/PublishedScope.php: -------------------------------------------------------------------------------- 1 | is_admin) { 19 | $builder->where('status', 'published') 20 | ->where('published_at', '<=', now()); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/Social/GithubCallbackController.php: -------------------------------------------------------------------------------- 1 | user(); 16 | 17 | $user = $action->handle('github', $githubUser); 18 | 19 | Auth::login($user); 20 | 21 | return redirect() 22 | ->route('app.feed.home'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/EmailVerificationPromptController.php: -------------------------------------------------------------------------------- 1 | user()->hasVerifiedEmail() 19 | ? redirect()->intended(route('app.feed.home', absolute: false)) 20 | : Inertia::render('Auth/VerifyEmail', ['status' => session('status')]); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Http/Requests/Application/Feed/ToggleVoteRequest.php: -------------------------------------------------------------------------------- 1 | |string> 21 | */ 22 | public function rules(): array 23 | { 24 | return [ 25 | 'vote_type' => 'required|in:upvote,downvote', 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Http/Resources/IndexResource.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | public function toArray(Request $request): array 16 | { 17 | return [ 18 | 'id' => $this->id, 19 | 'name' => $this->name, 20 | 'description' => $this->description, 21 | 'slug' => $this->slug, 22 | 'icon' => $this->getFirstMediaUrl(), 23 | 'color_code' => $this->color_code, 24 | 'status' => $this->status, 25 | ]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /database/settings/2025_08_06_233023_create_seo_settings.php: -------------------------------------------------------------------------------- 1 | migrator->add('seo.title', env('APP_NAME')); 10 | $this->migrator->add('seo.description', null); 11 | $this->migrator->add('seo.keywords', null); 12 | $this->migrator->add('seo.og_title', null); 13 | $this->migrator->add('seo.og_description', null); 14 | $this->migrator->add('seo.og_url', null); 15 | $this->migrator->add('seo.og_type', 'website'); 16 | $this->migrator->add('seo.twitter_title', null); 17 | $this->migrator->add('seo.twitter_description', null); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/EmailVerificationNotificationController.php: -------------------------------------------------------------------------------- 1 | user()->hasVerifiedEmail()) { 17 | return redirect()->intended(route('app.feed.home', absolute: false)); 18 | } 19 | 20 | $request->user()->sendEmailVerificationNotification(); 21 | 22 | return back()->with('status', 'verification-link-sent'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Rules/AdminCurrentPassword.php: -------------------------------------------------------------------------------- 1 | password)) { 22 | $fail(__('The current password is incorrect.')); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/message.blade.php: -------------------------------------------------------------------------------- 1 | 2 | {{-- Header --}} 3 | 4 | 5 | {{ config('app.name') }} 6 | 7 | 8 | 9 | {{-- Body --}} 10 | {{ $slot }} 11 | 12 | {{-- Subcopy --}} 13 | @isset($subcopy) 14 | 15 | 16 | {{ $subcopy }} 17 | 18 | 19 | @endisset 20 | 21 | {{-- Footer --}} 22 | 23 | 24 | © {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.') 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/Http/Resources/CategoryResource.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | public function toArray(Request $request): array 18 | { 19 | return [ 20 | 'id' => $this->id, 21 | 'name' => $this->name, 22 | 'slug' => $this->slug, 23 | 'meta_title' => $this->meta_title, 24 | 'meta_description' => $this->meta_description, 25 | 'packages_count' => $this->packages_count, 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /database/migrations/2025_03_06_003846_alter_indexes_table.php: -------------------------------------------------------------------------------- 1 | text('description')->nullable()->change(); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | 25 | Schema::table('indexes', function (Blueprint $table) { 26 | $table->text('description')->nullable(false)->change(); 27 | }); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /resources/css/sonner-overrides.css: -------------------------------------------------------------------------------- 1 | /* Custom overrides for sonner toast notifications */ 2 | :root { 3 | --toast-icon-margin: 0; 4 | --toast-height: auto !important; 5 | --normal-height: auto !important; 6 | } 7 | 8 | [data-sonner-toaster] { 9 | height: auto !important; 10 | max-height: 100vh !important; 11 | } 12 | 13 | [data-sonner-toast] { 14 | height: auto !important; 15 | min-height: 0 !important; 16 | display: flex !important; 17 | align-items: center !important; 18 | padding: 8px 12px !important; 19 | } 20 | 21 | [data-sonner-toast] [data-icon] { 22 | margin-right: 8px !important; 23 | display: inline-flex !important; 24 | } 25 | 26 | [data-sonner-toast] [data-title] { 27 | display: inline-flex !important; 28 | align-items: center !important; 29 | } 30 | -------------------------------------------------------------------------------- /resources/js/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from 'clsx' 2 | import { twMerge } from 'tailwind-merge' 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | 8 | export function capitalizeWords(str: string): string { 9 | return str.replace(/\b\w/g, (char) => char.toUpperCase()) 10 | } 11 | 12 | export const formatNumber = (num: number) => { 13 | if (num >= 1e6) return (num / 1e6).toFixed(1) + 'M' 14 | if (num >= 1e3) return (num / 1e3).toFixed(1) + 'K' 15 | return num.toString() 16 | } 17 | 18 | export function formatDate(date: string | Date): string { 19 | const d = new Date(date) 20 | return d.toLocaleDateString('en-US', { 21 | year: 'numeric', 22 | month: 'long', 23 | day: 'numeric', 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /app/Http/Controllers/Application/Feed/TogglePostBookmarkController.php: -------------------------------------------------------------------------------- 1 | execute($feedPost, $request->user()); 16 | 17 | return response()->json([ 18 | 'is_bookmarked' => $result['action'] === 'added', 19 | 'bookmarks_count' => $result['bookmarks_count'], 20 | 'action' => $result['action'], 21 | ]); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/GetPackageRepoDataController.php: -------------------------------------------------------------------------------- 1 | repository_url); 19 | 20 | return response()->json($repositoryData); 21 | } catch (\Throwable $th) { 22 | return response()->json(['error' => $th->getMessage()], 500); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /database/migrations/2025_07_15_192921_add_is_featured_to_packages_table.php: -------------------------------------------------------------------------------- 1 | boolean('is_featured') 16 | ->default(false); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | */ 23 | public function down(): void 24 | { 25 | Schema::table('packages', function (Blueprint $table) { 26 | $table->dropColumn('is_featured'); 27 | }); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /app/Http/Controllers/Application/Feed/FeedController.php: -------------------------------------------------------------------------------- 1 | all()) 17 | ->with(['source', 'comments.user', 'comments.replies.user']) 18 | ->latest('published_at') 19 | ->cursorPaginate(20); 20 | 21 | return Inertia::render('User/Feed', [ 22 | 'posts' => FeedPostResource::collection($posts), 23 | ]); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Http/Controllers/UserController.php: -------------------------------------------------------------------------------- 1 | whereId($request->user()->id)->withCount('packages')->first(); 19 | $popularBlogPosts = BlogPost::popularThisWeek(); 20 | 21 | return inertia('User/Index', [ 22 | 'user' => UserResource::make($user), 23 | 'popularBlogPosts' => BlogPostResource::collection($popularBlogPosts), 24 | ]); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /resources/js/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import { cn } from '@/lib/utils' 4 | 5 | const Textarea = React.forwardRef< 6 | HTMLTextAreaElement, 7 | React.ComponentProps<'textarea'> 8 | >(({ className, ...props }, ref) => { 9 | return ( 10 |