├── .cursor
└── rules
│ └── posthog-integration.mdc
├── .env.example
├── .gitignore
├── Dockerfile
├── README.md
├── bun.lockb
├── components.json
├── eslint.config.mjs
├── next.config.ts
├── package.json
├── pnpm-lock.yaml
├── postcss.config.mjs
├── prettier.config.js
├── prisma
├── migrations
│ ├── 20250212201806_init
│ │ └── migration.sql
│ ├── 20250305120831_post_and_nerd_added
│ │ └── migration.sql
│ ├── 20250305121810_post_naming
│ │ └── migration.sql
│ ├── 20250305123652_changes_title
│ │ └── migration.sql
│ ├── 20250312204245_access_post
│ │ └── migration.sql
│ ├── 20250312205052_default_access_post
│ │ └── migration.sql
│ ├── 20250313090028_user_about
│ │ └── migration.sql
│ ├── 20250313141440_first_time
│ │ └── migration.sql
│ ├── 20250313144606_country_add_user
│ │ └── migration.sql
│ ├── 20250315130859_post_commment_added
│ │ └── migration.sql
│ ├── 20250315133143_fix_post_comment
│ │ └── migration.sql
│ ├── 20250317204630_like_and_bookmark_post
│ │ └── migration.sql
│ ├── 20250317210229_bookmark_and_like
│ │ └── migration.sql
│ ├── 20250317214043_removed_bookmark_and_like
│ │ └── migration.sql
│ ├── 20250317214332_postlike
│ │ └── migration.sql
│ ├── 20250318084831_like
│ │ └── migration.sql
│ ├── 20250318092209_delete_relation_bookmark_and_like
│ │ └── migration.sql
│ ├── 20250319105708_report_added
│ │ └── migration.sql
│ ├── 20250320074027_media_added
│ │ └── migration.sql
│ ├── 20250320125628_midea_post_delete
│ │ └── migration.sql
│ ├── 20250324102053_cover_image
│ │ └── migration.sql
│ ├── 20250324103643_cover_image
│ │ └── migration.sql
│ ├── 20250324125706_add_follow_feature
│ │ └── migration.sql
│ ├── 20250324144631_following_added
│ │ └── migration.sql
│ ├── 20250325172417_add_projects_schema
│ │ └── migration.sql
│ ├── 20250326111725_categoty
│ │ └── migration.sql
│ ├── 20250326153217_project_image
│ │ └── migration.sql
│ ├── 20250328102830_user_id_inprojectupdate
│ │ └── migration.sql
│ ├── 20250329092445_deletion_for_reply_comments
│ │ └── migration.sql
│ ├── 20250329093601_share_project_update_as_post
│ │ └── migration.sql
│ ├── 20250329093736_share_project_update_as_post_update
│ │ └── migration.sql
│ ├── 20250329101716_share_project_update_as_post_2
│ │ └── migration.sql
│ ├── 20250331152000_community
│ │ └── migration.sql
│ ├── 20250409085146_following_follower_fixed
│ │ └── migration.sql
│ ├── 20250409101402_nerd_unique
│ │ └── migration.sql
│ ├── 20250409233203_add_original_post_id
│ │ └── migration.sql
│ ├── 20250409235112_sharepost
│ │ └── migration.sql
│ ├── 20250410005214_backup
│ │ └── migration.sql
│ ├── 20250410014100_working
│ │ └── migration.sql
│ ├── 20250411135652_add_shared_post_model
│ │ └── migration.sql
│ ├── 20250411140858_reverse
│ │ └── migration.sql
│ ├── 20250411141355_add_additional_context_to_report
│ │ └── migration.sql
│ ├── 20250419062030_added_bug_report
│ │ └── migration.sql
│ ├── 20250421124920_added_notification
│ │ └── migration.sql
│ ├── 20250421153152_add_notifications
│ │ └── migration.sql
│ ├── 20250422072157_updare
│ │ └── migration.sql
│ ├── 20250422090303_notification
│ │ └── migration.sql
│ ├── 20250422093720_update_nerdspace
│ │ └── migration.sql
│ └── migration_lock.toml
└── schema.prisma
├── public
├── 01.png
├── 03.png
├── 04.png
├── 05.png
├── Elon.png
├── Enistine.png
├── adawa.webp
├── art.jpg
├── auth-bg.png
├── bg9.jpg
├── build.jpg
├── fashion.jpg
├── file.svg
├── fonts
│ ├── ITCGaramondStd-BkCond.ttf
│ ├── ITCGaramondStd-LtCond.ttf
│ ├── ITCGaramondStd-LtCondIta.ttf
│ └── user.jpg
├── game.jpg
├── globe.svg
├── gwax.jpg
├── hu.webp
├── logo.jpg
├── music.jpg
├── nerd.jpg
├── next.svg
├── obsession.jpg
├── robot.jpg
├── user.jpg
├── user_placeholder.jpg
├── vercel.svg
└── window.svg
├── src
├── api
│ └── notification.ts
├── app
│ ├── (app)
│ │ ├── ai
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ ├── community
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ ├── event
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ ├── explore
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ ├── profile
│ │ │ ├── ProjectsTab.tsx
│ │ │ ├── [userId]
│ │ │ │ ├── followers
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── following
│ │ │ │ │ └── page.tsx
│ │ │ │ └── page.tsx
│ │ │ ├── layout.tsx
│ │ │ ├── page.tsx
│ │ │ └── user
│ │ │ │ └── [userId]
│ │ │ │ ├── followers
│ │ │ │ └── page.tsx
│ │ │ │ └── following
│ │ │ │ └── page.tsx
│ │ ├── project
│ │ │ ├── [id]
│ │ │ │ └── page.tsx
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ ├── sandbox
│ │ │ └── page.tsx
│ │ ├── user-profile
│ │ │ ├── [userId]
│ │ │ │ ├── followers
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── following
│ │ │ │ │ └── page.tsx
│ │ │ │ └── page.tsx
│ │ │ └── layout.tsx
│ │ └── whotofollow
│ │ │ └── page.tsx
│ ├── (auth)
│ │ ├── forget-password
│ │ │ └── page.tsx
│ │ ├── layout.tsx
│ │ ├── login
│ │ │ └── page.tsx
│ │ ├── reset-password
│ │ │ └── page.tsx
│ │ ├── signup
│ │ │ └── page.tsx
│ │ └── story
│ │ │ └── page.tsx
│ ├── api
│ │ ├── account
│ │ │ └── route.ts
│ │ ├── auth
│ │ │ └── [...all]
│ │ │ │ └── route.ts
│ │ ├── bug
│ │ │ └── route.ts
│ │ ├── community
│ │ │ ├── category
│ │ │ │ └── route.ts
│ │ │ └── route.ts
│ │ ├── dummy
│ │ │ └── route.ts
│ │ ├── explore
│ │ │ └── route.ts
│ │ ├── media-upload
│ │ │ └── route.ts
│ │ ├── notification
│ │ │ └── route.ts
│ │ ├── onboarding
│ │ │ ├── route.ts
│ │ │ └── status
│ │ │ │ └── route.ts
│ │ ├── post
│ │ │ ├── [id]
│ │ │ │ └── route.ts
│ │ │ ├── bookmark
│ │ │ │ └── route.ts
│ │ │ ├── comment
│ │ │ │ └── route.ts
│ │ │ ├── like
│ │ │ │ └── route.ts
│ │ │ ├── report
│ │ │ │ └── route.ts
│ │ │ └── route.ts
│ │ ├── project
│ │ │ ├── [id]
│ │ │ │ ├── route.ts
│ │ │ │ └── updates
│ │ │ │ │ └── route.ts
│ │ │ ├── follow
│ │ │ │ └── route.ts
│ │ │ ├── notification
│ │ │ │ └── route.ts
│ │ │ ├── rank
│ │ │ │ └── route.ts
│ │ │ ├── recommendation
│ │ │ │ └── route.ts
│ │ │ ├── review
│ │ │ │ └── route.ts
│ │ │ ├── route.ts
│ │ │ ├── star
│ │ │ │ └── route.ts
│ │ │ └── update
│ │ │ │ ├── [id]
│ │ │ │ └── route.ts
│ │ │ │ ├── comment
│ │ │ │ └── route.ts
│ │ │ │ ├── like
│ │ │ │ └── route.ts
│ │ │ │ └── share
│ │ │ │ └── route.ts
│ │ ├── route.ts
│ │ ├── security
│ │ │ └── route.ts
│ │ ├── user
│ │ │ ├── follow-notification
│ │ │ │ └── route.ts
│ │ │ ├── follow
│ │ │ │ ├── recommendation
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── posts
│ │ │ │ └── route.ts
│ │ │ └── route.ts
│ │ ├── users
│ │ │ ├── [userId]
│ │ │ │ ├── counts
│ │ │ │ │ └── route.ts
│ │ │ │ ├── followers
│ │ │ │ │ └── route.ts
│ │ │ │ ├── following
│ │ │ │ │ └── route.ts
│ │ │ │ ├── projects
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── bookmarks
│ │ │ │ └── route.ts
│ │ │ ├── check-follow
│ │ │ │ └── route.ts
│ │ │ ├── followers
│ │ │ │ └── route.ts
│ │ │ ├── following
│ │ │ │ └── route.ts
│ │ │ ├── projects
│ │ │ │ ├── owned
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ └── route.ts
│ │ └── whoami
│ │ │ ├── post
│ │ │ └── route.ts
│ │ │ ├── private
│ │ │ └── route.ts
│ │ │ └── route.ts
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ ├── onboarding
│ │ └── page.tsx
│ ├── post
│ │ └── [id]
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ ├── projects
│ │ └── recommendations
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ └── settings
│ │ ├── layout.tsx
│ │ └── page.tsx
├── components
│ ├── FeedbackButton.tsx
│ ├── FollowList.tsx
│ ├── NotificationDropdown.tsx
│ ├── UserList.tsx
│ ├── UserListSkeleton.tsx
│ ├── app-sidebar.tsx
│ ├── country-selector.tsx
│ ├── create-post-form.tsx
│ ├── custom
│ │ └── image-upload.tsx
│ ├── explore
│ │ ├── explore-cards.tsx
│ │ ├── explore-entry.tsx
│ │ ├── explore-post-card.tsx
│ │ ├── explore-render-post.tsx
│ │ └── project-explore-card.tsx
│ ├── follow-button.tsx
│ ├── forget-password.tsx
│ ├── get-started-form.tsx
│ ├── image-preview.tsx
│ ├── login-form.tsx
│ ├── media
│ │ ├── media-uploader.tsx
│ │ └── post-file-uploader.tsx
│ ├── mobile-view-message.tsx
│ ├── modal
│ │ ├── delete.modal.tsx
│ │ ├── edit.modal.tsx
│ │ └── report.modal.tsx
│ ├── nav-main.tsx
│ ├── nav-projects.tsx
│ ├── nav-secondary.tsx
│ ├── nav-user.tsx
│ ├── navbar.tsx
│ ├── navbar
│ │ ├── community_navbar.tsx
│ │ ├── left-navbar.tsx
│ │ ├── mobile-nav-bar.tsx
│ │ └── right-navbar.tsx
│ ├── onboarding-form.tsx
│ ├── onboarding
│ │ └── onboarding-prompt.tsx
│ ├── phone-input.tsx
│ ├── post-card.tsx
│ ├── post
│ │ ├── PostCard.tsx
│ │ ├── comment
│ │ │ ├── DeleteCommentModal.tsx
│ │ │ ├── EditCommentModal.tsx
│ │ │ ├── comment.tsx
│ │ │ └── render-comments.tsx
│ │ ├── post-card.tsx
│ │ ├── post-input.tsx
│ │ └── render-post.tsx
│ ├── project
│ │ ├── followed-projects.tsx
│ │ ├── leaderBorad.tsx
│ │ ├── project-card.tsx
│ │ ├── project-component.tsx
│ │ ├── project-detail.tsx
│ │ └── project-update-card.tsx
│ ├── quality-notice.tsx
│ ├── reset-password.tsx
│ ├── search-form.tsx
│ ├── settings-dialog.tsx
│ ├── settings
│ │ ├── account-setting-skeleton.tsx
│ │ ├── account-setting.tsx
│ │ ├── follow-button.tsx
│ │ ├── followers-following-list.tsx
│ │ ├── my-private-post.tsx
│ │ ├── myposts.tsx
│ │ ├── notification-setting.tsx
│ │ ├── profile-setting.tsx
│ │ ├── profile.tsx
│ │ ├── tabs
│ │ │ ├── BookmarksTab.tsx
│ │ │ ├── CollectionsTab.tsx
│ │ │ ├── PrivateTab.tsx
│ │ │ ├── ProjectsTab.tsx
│ │ │ └── UserProjectsTab.tsx
│ │ ├── therms-conditions.tsx
│ │ ├── user-posts.tsx
│ │ ├── user-profile-stats.tsx
│ │ └── user-profile.tsx
│ ├── sidebar
│ │ ├── nav-main.tsx
│ │ ├── nav-projects.tsx
│ │ ├── nav-secondary.tsx
│ │ └── nav-user.tsx
│ ├── signup-form.tsx
│ ├── site-header.tsx
│ ├── skeleton
│ │ ├── comment.skelton.tsx
│ │ ├── morepostFetch.skeleton.tsx
│ │ ├── project-card.tsx
│ │ ├── project-detail.skeleton.tsx
│ │ └── render-post.skeleton.tsx
│ ├── soon
│ │ ├── nerdspace-out.tsx
│ │ └── soon.tsx
│ ├── theme-provider.tsx
│ ├── theme-toggle.tsx
│ ├── toaster.tsx
│ ├── ui
│ │ ├── accordion.tsx
│ │ ├── alert-dialog.tsx
│ │ ├── alert.tsx
│ │ ├── aspect-ratio.tsx
│ │ ├── avatar.tsx
│ │ ├── badge.tsx
│ │ ├── breadcrumb.tsx
│ │ ├── button.tsx
│ │ ├── calendar.tsx
│ │ ├── card.tsx
│ │ ├── carousel.tsx
│ │ ├── chart.tsx
│ │ ├── checkbox.tsx
│ │ ├── collapsible.tsx
│ │ ├── command.tsx
│ │ ├── context-menu.tsx
│ │ ├── country-dropdown.tsx
│ │ ├── dialog.tsx
│ │ ├── drawer.tsx
│ │ ├── dropdown-menu.tsx
│ │ ├── edit-project-dialog.tsx
│ │ ├── emoji-picker.tsx
│ │ ├── form.tsx
│ │ ├── hover-card.tsx
│ │ ├── image-upload.tsx
│ │ ├── input-otp.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── loading-spinner.tsx
│ │ ├── menubar.tsx
│ │ ├── navigation-menu.tsx
│ │ ├── pagination.tsx
│ │ ├── popover.tsx
│ │ ├── progress.tsx
│ │ ├── radio-group.tsx
│ │ ├── resizable.tsx
│ │ ├── resizeble-text-area.tsx
│ │ ├── scroll-area.tsx
│ │ ├── select.tsx
│ │ ├── separator.tsx
│ │ ├── sheet.tsx
│ │ ├── sidebar.tsx
│ │ ├── skeleton.tsx
│ │ ├── slider.tsx
│ │ ├── sonner.tsx
│ │ ├── switch.tsx
│ │ ├── table.tsx
│ │ ├── tabs.tsx
│ │ ├── textarea.tsx
│ │ ├── toast.tsx
│ │ ├── toaster.tsx
│ │ ├── toggle-group.tsx
│ │ ├── toggle.tsx
│ │ └── tooltip.tsx
│ └── user
│ │ ├── FollowListSkeleton.tsx
│ │ ├── community.tsx
│ │ ├── followList.tsx
│ │ ├── follower.tsx
│ │ ├── following.tsx
│ │ ├── recommend-project-page.tsx
│ │ ├── recommend-project.tsx
│ │ └── trending.tsx
├── functions
│ ├── access-change-post.ts
│ ├── calculate-time-difference.ts
│ ├── color-extractor.ts
│ ├── create-post.ts
│ ├── delete-post.ts
│ ├── edit-post.ts
│ ├── fetch-my-post.ts
│ ├── fetch-my-private-post.ts
│ ├── fetch-post.ts
│ ├── fetchProject.ts
│ ├── follow.ts
│ ├── format-number.ts
│ ├── get-notification.ts
│ ├── get-user.ts
│ ├── get-who-am-i.ts
│ ├── like-post.ts
│ └── render-helper.ts
├── hooks
│ ├── use-communities.ts
│ ├── use-community-posts.ts
│ ├── use-debounce.ts
│ ├── use-mobile.tsx
│ ├── use-toast-notifications.ts
│ ├── use-toast.ts
│ └── useFetchPosts.tsx
├── interface
│ └── auth
│ │ ├── comment.interface.ts
│ │ ├── community.interface.ts
│ │ ├── onboarding.interface.ts
│ │ ├── post.interface.ts
│ │ ├── project.interface.ts
│ │ ├── projectInterface
│ │ ├── signin.interface.ts
│ │ ├── signup.interface.ts
│ │ └── user.interface.ts
├── lib
│ ├── auth-client.ts
│ ├── auth.ts
│ ├── axios.ts
│ ├── emailTemplates
│ │ ├── baseTemplate.ts
│ │ └── templates.ts
│ ├── posthog-provider.tsx
│ ├── posthog.ts
│ ├── prisma.ts
│ ├── providers.tsx
│ ├── sendEmail.ts
│ ├── types.ts
│ ├── utils.ts
│ ├── validations
│ │ └── notification.ts
│ └── validators
│ │ └── community.ts
├── middleware.ts
├── providers
│ ├── day-picker-provider.tsx
│ ├── tanstack-query-provider.tsx
│ └── who-am-i-provider.tsx
├── query
│ └── post-query.ts
├── script
│ └── dummy-users.ts
├── store
│ ├── community.store.ts
│ ├── data.store.ts
│ ├── explore.store.ts
│ ├── fileStore.ts
│ ├── post.store.ts
│ ├── project-update.store.ts
│ ├── report.strore.ts
│ ├── search.store.ts
│ ├── useFormStore.ts
│ ├── user.store.ts
│ └── userProfile.store.ts
├── types
│ └── apiResponses.ts
└── validation
│ ├── bookmark.validation.ts
│ ├── bug.validation.ts
│ ├── comment.validation.ts
│ ├── like.validation.ts
│ ├── login.validation.ts
│ ├── post.validation.ts
│ ├── project.validation.ts
│ ├── report.validation.ts
│ ├── reset-pass.validation.ts
│ ├── signup.validation.ts
│ └── verify-email.ts
├── tailwind.config.ts
├── tsconfig.json
└── vercel.json
/.cursor/rules/posthog-integration.mdc:
--------------------------------------------------------------------------------
1 | ---
2 | description: apply when interacting with PostHog/analytics tasks
3 | globs:
4 | alwaysApply: true
5 | ---
6 |
7 | Never hallucinate an API key. Instead, always use the API key populated in the .env file.
8 |
9 | # Feature flags
10 |
11 | A given feature flag should be used in as few places as possible. Do not increase the risk of undefined behavior by scattering the same feature flag across multiple areas of code. If the same feature flag needs to be introduced at multiple callsites, flag this for the developer to inspect carefully.
12 |
13 | If a job requires creating new feature flag names, make them as clear and descriptive as possible.
14 |
15 | If using TypeScript, use an enum to store flag names. If using JavaScript, store flag names as strings to an object declared as a constant, to simulate an enum. Use a consistent naming convention for this storage. enum/const object members should be written UPPERCASE_WITH_UNDERSCORE.
16 |
17 | Gate flag-dependent code on a check that verifies the flag's values are valid and expected.
18 |
19 | # Custom properties
20 |
21 | If a custom property for a person or event is at any point referenced in two or more files or two or more callsites in the same file, use an enum or const object, as above in feature flags.
22 |
23 | # Naming
24 |
25 | Before creating any new event or property names, consult with the developer for any existing naming convention. Consistency in naming is essential, and additional context may exist outside this project. Similarly, be careful about any changes to existing event and property names, as this may break reporting and distort data for the project.
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | # Database connection URL for PostgreSQL
2 | DATABASE_URL="postgresql://username:password@localhost:5432/database_name?schema=public"
3 |
4 | # Authentication configuration
5 | BETTER_AUTH_SECRET=your_32_character_secret_key_here
6 | BETTER_AUTH_URL=http://localhost:3000
7 |
8 | # GitHub OAuth credentials
9 | GITHUB_CLIENT_ID=your_github_client_id
10 | GITHUB_CLIENT_SECRET=your_github_client_secret
11 |
12 | # Cloudinary configuration for image uploads
13 | NEXT_PUBLIC_CLOUDINARY_UPLOAD_URL=your_cloudinary_upload_url
14 | NEXT_PUBLIC_CLOUDINARY_UPLOAD_PRESET=your_upload_preset_name
15 | CLOUDINARY_URL=cloudinary://api_key:api_secret@cloud_name
16 |
17 | # Base URL for the application
18 | # NEXT_PUBLIC_BASE_URL=http://localhost:3000
19 |
20 | # Environment mode
21 | NODE_ENV=development
22 |
23 | # Email configuration for sending emails
24 | EMAIL_USER=your_email@example.com
25 | EMAIL_PASS=your_email_app_specific_password || if_enableed_2factor_auth_you'll_need_to_generate_app_specific_password
26 |
27 | # PostHog analytics key
28 | NEXT_PUBLIC_POSTHOG_KEY=your_posthog_project_key || if_analytics_are_enabled_in_posthog_dashboard
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.*
7 | .yarn/*
8 | !.yarn/patches
9 | !.yarn/plugins
10 | !.yarn/releases
11 | !.yarn/versions
12 |
13 | # testing
14 | /coverage
15 |
16 | # next.js
17 | /.next/
18 | /out/
19 |
20 | # production
21 | /build
22 | /dist
23 |
24 | # misc
25 | .DS_Store
26 | *.pem
27 | .idea/
28 | *.log
29 | *.lock
30 | .cache
31 | .env
32 | .env.local
33 | .env.development.local
34 | .env.test.local
35 | .env.production.local
36 |
37 | # debug
38 | npm-debug.log*
39 | yarn-debug.log*
40 | yarn-error.log*
41 | .pnpm-debug.log*
42 |
43 | # vercel
44 | .vercel
45 |
46 | # typescript
47 | *.tsbuildinfo
48 | next-env.d.ts
49 |
50 | # Optional
51 | .turbo
52 | .swc/
53 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/Dockerfile
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20 |
21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22 |
23 | ## Learn More
24 |
25 | To learn more about Next.js, take a look at the following resources:
26 |
27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29 |
30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31 |
32 | ## Deploy on Vercel
33 |
34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35 |
36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
37 |
--------------------------------------------------------------------------------
/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/bun.lockb
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "src/app/globals.css",
9 | "baseColor": "zinc",
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 | }
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { dirname } from "path";
2 | import { fileURLToPath } from "url";
3 | import { FlatCompat } from "@eslint/eslintrc";
4 |
5 | const __filename = fileURLToPath(import.meta.url);
6 | const __dirname = dirname(__filename);
7 |
8 | const compat = new FlatCompat({
9 | baseDirectory: __dirname,
10 | });
11 |
12 | const eslintConfig = [
13 | ...compat.extends("next/core-web-vitals", "next/typescript", "prettier"),
14 | ...compat.config({
15 | rules: {
16 | "@typescript-eslint/no-unused-vars": "off",
17 | "@typescript-eslint/no-explicit-any": "off",
18 | },
19 | }),
20 | ];
21 |
22 | export default eslintConfig;
23 |
--------------------------------------------------------------------------------
/next.config.ts:
--------------------------------------------------------------------------------
1 | import type { NextConfig } from "next";
2 |
3 | const nextConfig: NextConfig = {
4 | /* config options here */
5 | images: {
6 | domains: [
7 | "avatars.githubusercontent.com",
8 | "res.cloudinary.com",
9 | "images.unsplash.com",
10 | "play-lh.googleusercontent.com",
11 | ],
12 | // loader: "cloudinary",
13 | // path: "https://api.cloudinary.com/v1_1/dsaitxphg/image/upload",
14 | },
15 |
16 | // We remove ESLint
17 | // run Biome and TypeScript separately in the CI pipeline
18 | // eslint: {
19 | // ignoreDuringBuilds: true,
20 | // },
21 | // typescript: {
22 | // ignoreBuildErrors: true,
23 | // },
24 |
25 | experimental: {
26 | // nodeMiddleware: true,
27 | staleTimes: {
28 | dynamic: 30,
29 | },
30 | },
31 | async rewrites() {
32 | return [
33 | {
34 | source: "/ingest/static/:path*",
35 | destination: "https://us-assets.i.posthog.com/static/:path*",
36 | },
37 | {
38 | source: "/ingest/:path*",
39 | destination: "https://us.i.posthog.com/:path*",
40 | },
41 | {
42 | source: "/ingest/decide",
43 | destination: "https://us.i.posthog.com/decide",
44 | },
45 | ];
46 | },
47 | // This is required to support PostHog trailing slash API requests
48 | skipTrailingSlashRedirect: true,
49 | };
50 |
51 | export default nextConfig;
52 |
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: ["prettier-plugin-tailwindcss"],
3 | };
4 |
--------------------------------------------------------------------------------
/prisma/migrations/20250305120831_post_and_nerd_added/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "user" ADD COLUMN "nerdAt" TEXT;
3 |
4 | -- CreateTable
5 | CREATE TABLE "post" (
6 | "id" TEXT NOT NULL,
7 | "content" TEXT NOT NULL,
8 | "createdAt" TIMESTAMP(3) NOT NULL,
9 | "updatedAt" TIMESTAMP(3) NOT NULL,
10 | "userId" TEXT NOT NULL,
11 |
12 | CONSTRAINT "post_pkey" PRIMARY KEY ("id")
13 | );
14 |
15 | -- AddForeignKey
16 | ALTER TABLE "post" ADD CONSTRAINT "post_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
17 |
--------------------------------------------------------------------------------
/prisma/migrations/20250305121810_post_naming/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - You are about to drop the `post` table. If the table is not empty, all the data it contains will be lost.
5 |
6 | */
7 | -- DropForeignKey
8 | ALTER TABLE "post" DROP CONSTRAINT "post_userId_fkey";
9 |
10 | -- DropTable
11 | DROP TABLE "post";
12 |
13 | -- CreateTable
14 | CREATE TABLE "posts" (
15 | "id" TEXT NOT NULL,
16 | "content" TEXT NOT NULL,
17 | "createdAt" TIMESTAMP(3) NOT NULL,
18 | "updatedAt" TIMESTAMP(3) NOT NULL,
19 | "userId" TEXT NOT NULL,
20 |
21 | CONSTRAINT "posts_pkey" PRIMARY KEY ("id")
22 | );
23 |
24 | -- AddForeignKey
25 | ALTER TABLE "posts" ADD CONSTRAINT "posts_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
26 |
--------------------------------------------------------------------------------
/prisma/migrations/20250305123652_changes_title/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "posts" ALTER COLUMN "createdAt" SET DEFAULT CURRENT_TIMESTAMP,
3 | ALTER COLUMN "updatedAt" SET DEFAULT CURRENT_TIMESTAMP;
4 |
--------------------------------------------------------------------------------
/prisma/migrations/20250312204245_access_post/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - Added the required column `access` to the `posts` table without a default value. This is not possible if the table is not empty.
5 |
6 | */
7 | -- CreateEnum
8 | CREATE TYPE "PostAccess" AS ENUM ('private', 'public');
9 |
10 | -- AlterTable
11 | ALTER TABLE "posts" ADD COLUMN "access" "PostAccess" NOT NULL;
12 |
--------------------------------------------------------------------------------
/prisma/migrations/20250312205052_default_access_post/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "posts" ALTER COLUMN "access" SET DEFAULT 'public';
3 |
--------------------------------------------------------------------------------
/prisma/migrations/20250313090028_user_about/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "user" ADD COLUMN "bio" TEXT,
3 | ADD COLUMN "country" TEXT,
4 | ADD COLUMN "link" TEXT,
5 | ADD COLUMN "visualName" TEXT;
6 |
--------------------------------------------------------------------------------
/prisma/migrations/20250313141440_first_time/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "user" ADD COLUMN "firstTime" BOOLEAN NOT NULL DEFAULT true;
3 |
--------------------------------------------------------------------------------
/prisma/migrations/20250313144606_country_add_user/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - You are about to drop the column `country` on the `user` table. All the data in the column will be lost.
5 |
6 | */
7 | -- AlterTable
8 | ALTER TABLE "user" DROP COLUMN "country",
9 | ADD COLUMN "countryId" TEXT;
10 |
11 | -- CreateTable
12 | CREATE TABLE "country" (
13 | "id" TEXT NOT NULL,
14 | "alpha2" TEXT NOT NULL,
15 | "alpha3" TEXT NOT NULL,
16 | "countryCallingCodes" TEXT[],
17 | "currencies" TEXT[],
18 | "emoji" TEXT,
19 | "ioc" TEXT NOT NULL,
20 | "languages" TEXT[],
21 | "name" TEXT NOT NULL,
22 | "status" TEXT NOT NULL,
23 | "userId" TEXT NOT NULL,
24 |
25 | CONSTRAINT "country_pkey" PRIMARY KEY ("id")
26 | );
27 |
28 | -- CreateIndex
29 | CREATE UNIQUE INDEX "country_userId_key" ON "country"("userId");
30 |
31 | -- AddForeignKey
32 | ALTER TABLE "country" ADD CONSTRAINT "country_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
33 |
--------------------------------------------------------------------------------
/prisma/migrations/20250315130859_post_commment_added/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE "postcomments" (
3 | "id" TEXT NOT NULL,
4 | "userId" TEXT NOT NULL,
5 | "postId" TEXT NOT NULL,
6 | "parentId" TEXT,
7 | "content" TEXT NOT NULL,
8 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
9 |
10 | CONSTRAINT "postcomments_pkey" PRIMARY KEY ("id")
11 | );
12 |
13 | -- AddForeignKey
14 | ALTER TABLE "postcomments" ADD CONSTRAINT "postcomments_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "postcomments"("id") ON DELETE SET NULL ON UPDATE CASCADE;
15 |
16 | -- AddForeignKey
17 | ALTER TABLE "postcomments" ADD CONSTRAINT "postcomments_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
18 |
19 | -- AddForeignKey
20 | ALTER TABLE "postcomments" ADD CONSTRAINT "postcomments_postId_fkey" FOREIGN KEY ("postId") REFERENCES "posts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
21 |
--------------------------------------------------------------------------------
/prisma/migrations/20250315133143_fix_post_comment/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "postcomments" ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
3 |
--------------------------------------------------------------------------------
/prisma/migrations/20250317204630_like_and_bookmark_post/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "postcomments" ALTER COLUMN "updatedAt" DROP DEFAULT;
3 |
4 | -- CreateTable
5 | CREATE TABLE "postlikes" (
6 | "id" TEXT NOT NULL,
7 | "userId" TEXT NOT NULL,
8 | "postId" TEXT NOT NULL,
9 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
10 |
11 | CONSTRAINT "postlikes_pkey" PRIMARY KEY ("id")
12 | );
13 |
14 | -- CreateTable
15 | CREATE TABLE "postbookmarks" (
16 | "id" TEXT NOT NULL,
17 | "userId" TEXT NOT NULL,
18 | "postId" TEXT NOT NULL,
19 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
20 |
21 | CONSTRAINT "postbookmarks_pkey" PRIMARY KEY ("id")
22 | );
23 |
24 | -- AddForeignKey
25 | ALTER TABLE "postlikes" ADD CONSTRAINT "postlikes_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
26 |
27 | -- AddForeignKey
28 | ALTER TABLE "postlikes" ADD CONSTRAINT "postlikes_postId_fkey" FOREIGN KEY ("postId") REFERENCES "posts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
29 |
30 | -- AddForeignKey
31 | ALTER TABLE "postbookmarks" ADD CONSTRAINT "postbookmarks_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
32 |
33 | -- AddForeignKey
34 | ALTER TABLE "postbookmarks" ADD CONSTRAINT "postbookmarks_postId_fkey" FOREIGN KEY ("postId") REFERENCES "posts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
35 |
--------------------------------------------------------------------------------
/prisma/migrations/20250317210229_bookmark_and_like/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - A unique constraint covering the columns `[userId,postId]` on the table `postbookmarks` will be added. If there are existing duplicate values, this will fail.
5 | - A unique constraint covering the columns `[userId,postId]` on the table `postlikes` will be added. If there are existing duplicate values, this will fail.
6 |
7 | */
8 | -- CreateIndex
9 | CREATE UNIQUE INDEX "postbookmarks_userId_postId_key" ON "postbookmarks"("userId", "postId");
10 |
11 | -- CreateIndex
12 | CREATE UNIQUE INDEX "postlikes_userId_postId_key" ON "postlikes"("userId", "postId");
13 |
--------------------------------------------------------------------------------
/prisma/migrations/20250317214043_removed_bookmark_and_like/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - You are about to drop the `postbookmarks` table. If the table is not empty, all the data it contains will be lost.
5 | - You are about to drop the `postlikes` table. If the table is not empty, all the data it contains will be lost.
6 |
7 | */
8 | -- DropForeignKey
9 | ALTER TABLE "postbookmarks" DROP CONSTRAINT "postbookmarks_postId_fkey";
10 |
11 | -- DropForeignKey
12 | ALTER TABLE "postbookmarks" DROP CONSTRAINT "postbookmarks_userId_fkey";
13 |
14 | -- DropForeignKey
15 | ALTER TABLE "postlikes" DROP CONSTRAINT "postlikes_postId_fkey";
16 |
17 | -- DropForeignKey
18 | ALTER TABLE "postlikes" DROP CONSTRAINT "postlikes_userId_fkey";
19 |
20 | -- DropTable
21 | DROP TABLE "postbookmarks";
22 |
23 | -- DropTable
24 | DROP TABLE "postlikes";
25 |
--------------------------------------------------------------------------------
/prisma/migrations/20250317214332_postlike/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE "postlikes" (
3 | "id" TEXT NOT NULL,
4 | "userId" TEXT NOT NULL,
5 | "postId" TEXT NOT NULL,
6 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
7 |
8 | CONSTRAINT "postlikes_pkey" PRIMARY KEY ("id")
9 | );
10 |
11 | -- CreateIndex
12 | CREATE UNIQUE INDEX "postlikes_userId_postId_key" ON "postlikes"("userId", "postId");
13 |
14 | -- AddForeignKey
15 | ALTER TABLE "postlikes" ADD CONSTRAINT "postlikes_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
16 |
17 | -- AddForeignKey
18 | ALTER TABLE "postlikes" ADD CONSTRAINT "postlikes_postId_fkey" FOREIGN KEY ("postId") REFERENCES "posts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
19 |
--------------------------------------------------------------------------------
/prisma/migrations/20250318084831_like/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - You are about to drop the `postlikes` table. If the table is not empty, all the data it contains will be lost.
5 |
6 | */
7 | -- DropForeignKey
8 | ALTER TABLE "postlikes" DROP CONSTRAINT "postlikes_postId_fkey";
9 |
10 | -- DropForeignKey
11 | ALTER TABLE "postlikes" DROP CONSTRAINT "postlikes_userId_fkey";
12 |
13 | -- DropTable
14 | DROP TABLE "postlikes";
15 |
16 | -- CreateTable
17 | CREATE TABLE "Like" (
18 | "id" TEXT NOT NULL,
19 | "userId" TEXT NOT NULL,
20 | "postId" TEXT NOT NULL,
21 |
22 | CONSTRAINT "Like_pkey" PRIMARY KEY ("id")
23 | );
24 |
25 | -- CreateTable
26 | CREATE TABLE "Bookmark" (
27 | "id" TEXT NOT NULL,
28 | "userId" TEXT NOT NULL,
29 | "postId" TEXT NOT NULL,
30 |
31 | CONSTRAINT "Bookmark_pkey" PRIMARY KEY ("id")
32 | );
33 |
34 | -- CreateIndex
35 | CREATE UNIQUE INDEX "Like_userId_postId_key" ON "Like"("userId", "postId");
36 |
37 | -- CreateIndex
38 | CREATE UNIQUE INDEX "Bookmark_userId_postId_key" ON "Bookmark"("userId", "postId");
39 |
40 | -- AddForeignKey
41 | ALTER TABLE "Like" ADD CONSTRAINT "Like_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
42 |
43 | -- AddForeignKey
44 | ALTER TABLE "Like" ADD CONSTRAINT "Like_postId_fkey" FOREIGN KEY ("postId") REFERENCES "posts"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
45 |
46 | -- AddForeignKey
47 | ALTER TABLE "Bookmark" ADD CONSTRAINT "Bookmark_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
48 |
49 | -- AddForeignKey
50 | ALTER TABLE "Bookmark" ADD CONSTRAINT "Bookmark_postId_fkey" FOREIGN KEY ("postId") REFERENCES "posts"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
51 |
--------------------------------------------------------------------------------
/prisma/migrations/20250318092209_delete_relation_bookmark_and_like/migration.sql:
--------------------------------------------------------------------------------
1 | -- DropForeignKey
2 | ALTER TABLE "Bookmark" DROP CONSTRAINT "Bookmark_postId_fkey";
3 |
4 | -- DropForeignKey
5 | ALTER TABLE "Bookmark" DROP CONSTRAINT "Bookmark_userId_fkey";
6 |
7 | -- DropForeignKey
8 | ALTER TABLE "Like" DROP CONSTRAINT "Like_postId_fkey";
9 |
10 | -- DropForeignKey
11 | ALTER TABLE "Like" DROP CONSTRAINT "Like_userId_fkey";
12 |
13 | -- AddForeignKey
14 | ALTER TABLE "Like" ADD CONSTRAINT "Like_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
15 |
16 | -- AddForeignKey
17 | ALTER TABLE "Like" ADD CONSTRAINT "Like_postId_fkey" FOREIGN KEY ("postId") REFERENCES "posts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
18 |
19 | -- AddForeignKey
20 | ALTER TABLE "Bookmark" ADD CONSTRAINT "Bookmark_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
21 |
22 | -- AddForeignKey
23 | ALTER TABLE "Bookmark" ADD CONSTRAINT "Bookmark_postId_fkey" FOREIGN KEY ("postId") REFERENCES "posts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
24 |
--------------------------------------------------------------------------------
/prisma/migrations/20250319105708_report_added/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateEnum
2 | CREATE TYPE "ReportStatus" AS ENUM ('PENDING', 'REVIEWED', 'RESOLVED', 'REJECTED');
3 |
4 | -- CreateTable
5 | CREATE TABLE "reports" (
6 | "id" TEXT NOT NULL,
7 | "reporterId" TEXT NOT NULL,
8 | "postId" TEXT,
9 | "commentId" TEXT,
10 | "reason" TEXT NOT NULL,
11 | "status" "ReportStatus" NOT NULL DEFAULT 'PENDING',
12 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
13 | "updatedAt" TIMESTAMP(3) NOT NULL,
14 |
15 | CONSTRAINT "reports_pkey" PRIMARY KEY ("id")
16 | );
17 |
18 | -- AddForeignKey
19 | ALTER TABLE "reports" ADD CONSTRAINT "reports_reporterId_fkey" FOREIGN KEY ("reporterId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
20 |
21 | -- AddForeignKey
22 | ALTER TABLE "reports" ADD CONSTRAINT "reports_postId_fkey" FOREIGN KEY ("postId") REFERENCES "posts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
23 |
24 | -- AddForeignKey
25 | ALTER TABLE "reports" ADD CONSTRAINT "reports_commentId_fkey" FOREIGN KEY ("commentId") REFERENCES "postcomments"("id") ON DELETE CASCADE ON UPDATE CASCADE;
26 |
--------------------------------------------------------------------------------
/prisma/migrations/20250320074027_media_added/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateEnum
2 | CREATE TYPE "MediaType" AS ENUM ('IMAGE', 'VIDEO', 'GIF');
3 |
4 | -- CreateTable
5 | CREATE TABLE "Media" (
6 | "id" TEXT NOT NULL,
7 | "url" TEXT NOT NULL,
8 | "type" "MediaType" NOT NULL,
9 | "postId" TEXT NOT NULL,
10 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
11 |
12 | CONSTRAINT "Media_pkey" PRIMARY KEY ("id")
13 | );
14 |
15 | -- AddForeignKey
16 | ALTER TABLE "Media" ADD CONSTRAINT "Media_postId_fkey" FOREIGN KEY ("postId") REFERENCES "posts"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
17 |
--------------------------------------------------------------------------------
/prisma/migrations/20250320125628_midea_post_delete/migration.sql:
--------------------------------------------------------------------------------
1 | -- DropForeignKey
2 | ALTER TABLE "Media" DROP CONSTRAINT "Media_postId_fkey";
3 |
4 | -- AddForeignKey
5 | ALTER TABLE "Media" ADD CONSTRAINT "Media_postId_fkey" FOREIGN KEY ("postId") REFERENCES "posts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
6 |
--------------------------------------------------------------------------------
/prisma/migrations/20250324102053_cover_image/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "posts" ADD COLUMN "coverImage" TEXT;
3 |
--------------------------------------------------------------------------------
/prisma/migrations/20250324103643_cover_image/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - You are about to drop the column `coverImage` on the `posts` table. All the data in the column will be lost.
5 |
6 | */
7 | -- AlterTable
8 | ALTER TABLE "posts" DROP COLUMN "coverImage";
9 |
10 | -- AlterTable
11 | ALTER TABLE "user" ADD COLUMN "coverImage" TEXT;
12 |
--------------------------------------------------------------------------------
/prisma/migrations/20250324125706_add_follow_feature/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE "Follow" (
3 | "id" TEXT NOT NULL,
4 | "followerId" TEXT NOT NULL,
5 | "followingId" TEXT NOT NULL,
6 |
7 | CONSTRAINT "Follow_pkey" PRIMARY KEY ("id")
8 | );
9 |
10 | -- CreateIndex
11 | CREATE UNIQUE INDEX "Follow_followerId_followingId_key" ON "Follow"("followerId", "followingId");
12 |
13 | -- AddForeignKey
14 | ALTER TABLE "Follow" ADD CONSTRAINT "Follow_followerId_fkey" FOREIGN KEY ("followerId") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
15 |
16 | -- AddForeignKey
17 | ALTER TABLE "Follow" ADD CONSTRAINT "Follow_followingId_fkey" FOREIGN KEY ("followingId") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
18 |
--------------------------------------------------------------------------------
/prisma/migrations/20250324144631_following_added/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - You are about to drop the `Follow` table. If the table is not empty, all the data it contains will be lost.
5 |
6 | */
7 | -- DropForeignKey
8 | ALTER TABLE "Follow" DROP CONSTRAINT "Follow_followerId_fkey";
9 |
10 | -- DropForeignKey
11 | ALTER TABLE "Follow" DROP CONSTRAINT "Follow_followingId_fkey";
12 |
13 | -- DropTable
14 | DROP TABLE "Follow";
15 |
16 | -- CreateTable
17 | CREATE TABLE "Follows" (
18 | "id" TEXT NOT NULL,
19 | "followerId" TEXT NOT NULL,
20 | "followingId" TEXT NOT NULL,
21 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
22 |
23 | CONSTRAINT "Follows_pkey" PRIMARY KEY ("id")
24 | );
25 |
26 | -- CreateIndex
27 | CREATE UNIQUE INDEX "Follows_followerId_followingId_key" ON "Follows"("followerId", "followingId");
28 |
29 | -- AddForeignKey
30 | ALTER TABLE "Follows" ADD CONSTRAINT "Follows_followerId_fkey" FOREIGN KEY ("followerId") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
31 |
32 | -- AddForeignKey
33 | ALTER TABLE "Follows" ADD CONSTRAINT "Follows_followingId_fkey" FOREIGN KEY ("followingId") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
34 |
--------------------------------------------------------------------------------
/prisma/migrations/20250326111725_categoty/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - The `category` column on the `Project` table would be dropped and recreated. This will lead to data loss if there is data in the column.
5 |
6 | */
7 | -- AlterTable
8 | ALTER TABLE "Project" DROP COLUMN "category",
9 | ADD COLUMN "category" TEXT[];
10 |
--------------------------------------------------------------------------------
/prisma/migrations/20250326153217_project_image/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - Added the required column `image` to the `Project` table without a default value. This is not possible if the table is not empty.
5 |
6 | */
7 | -- AlterTable
8 | ALTER TABLE "Project" ADD COLUMN "image" TEXT NOT NULL;
9 |
--------------------------------------------------------------------------------
/prisma/migrations/20250328102830_user_id_inprojectupdate/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "ProjectUpdate" ADD COLUMN "userId" TEXT;
3 |
4 | -- AddForeignKey
5 | ALTER TABLE "ProjectUpdate" ADD CONSTRAINT "ProjectUpdate_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE CASCADE;
6 |
--------------------------------------------------------------------------------
/prisma/migrations/20250329092445_deletion_for_reply_comments/migration.sql:
--------------------------------------------------------------------------------
1 | -- DropForeignKey
2 | ALTER TABLE "postcomments" DROP CONSTRAINT "postcomments_parentId_fkey";
3 |
4 | -- AddForeignKey
5 | ALTER TABLE "postcomments" ADD CONSTRAINT "postcomments_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "postcomments"("id") ON DELETE CASCADE ON UPDATE CASCADE;
6 |
--------------------------------------------------------------------------------
/prisma/migrations/20250329093601_share_project_update_as_post/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "posts" ADD COLUMN "projectId" TEXT,
3 | ADD COLUMN "shared" BOOLEAN;
4 |
--------------------------------------------------------------------------------
/prisma/migrations/20250329093736_share_project_update_as_post_update/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "posts" ALTER COLUMN "shared" SET DEFAULT false;
3 |
--------------------------------------------------------------------------------
/prisma/migrations/20250329101716_share_project_update_as_post_2/migration.sql:
--------------------------------------------------------------------------------
1 | -- AddForeignKey
2 | ALTER TABLE "posts" ADD CONSTRAINT "posts_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE SET NULL ON UPDATE CASCADE;
3 |
--------------------------------------------------------------------------------
/prisma/migrations/20250409085146_following_follower_fixed/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE "_UserInterests" (
3 | "A" TEXT NOT NULL,
4 | "B" TEXT NOT NULL,
5 |
6 | CONSTRAINT "_UserInterests_AB_pkey" PRIMARY KEY ("A","B")
7 | );
8 |
9 | -- CreateIndex
10 | CREATE INDEX "_UserInterests_B_index" ON "_UserInterests"("B");
11 |
12 | -- AddForeignKey
13 | ALTER TABLE "_UserInterests" ADD CONSTRAINT "_UserInterests_A_fkey" FOREIGN KEY ("A") REFERENCES "CommunityCategory"("id") ON DELETE CASCADE ON UPDATE CASCADE;
14 |
15 | -- AddForeignKey
16 | ALTER TABLE "_UserInterests" ADD CONSTRAINT "_UserInterests_B_fkey" FOREIGN KEY ("B") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
17 |
--------------------------------------------------------------------------------
/prisma/migrations/20250409101402_nerd_unique/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - A unique constraint covering the columns `[nerdAt]` on the table `user` will be added. If there are existing duplicate values, this will fail.
5 |
6 | */
7 | -- CreateIndex
8 | CREATE UNIQUE INDEX "user_nerdAt_key" ON "user"("nerdAt");
9 |
--------------------------------------------------------------------------------
/prisma/migrations/20250409233203_add_original_post_id/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "posts" ADD COLUMN "originalPostId" TEXT;
3 |
4 | -- AddForeignKey
5 | ALTER TABLE "posts" ADD CONSTRAINT "posts_originalPostId_fkey" FOREIGN KEY ("originalPostId") REFERENCES "posts"("id") ON DELETE SET NULL ON UPDATE CASCADE;
6 |
--------------------------------------------------------------------------------
/prisma/migrations/20250409235112_sharepost/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - You are about to drop the column `originalPostId` on the `posts` table. All the data in the column will be lost.
5 | - You are about to drop the `Media` table. If the table is not empty, all the data it contains will be lost.
6 |
7 | */
8 | -- DropForeignKey
9 | ALTER TABLE "Media" DROP CONSTRAINT "Media_postId_fkey";
10 |
11 | -- DropForeignKey
12 | ALTER TABLE "posts" DROP CONSTRAINT "posts_originalPostId_fkey";
13 |
14 | -- AlterTable
15 | ALTER TABLE "posts" DROP COLUMN "originalPostId",
16 | ADD COLUMN "sharedContent" TEXT,
17 | ADD COLUMN "sharedFromId" TEXT;
18 |
19 | -- DropTable
20 | DROP TABLE "Media";
21 |
22 | -- CreateTable
23 | CREATE TABLE "media" (
24 | "id" TEXT NOT NULL,
25 | "url" TEXT NOT NULL,
26 | "type" "MediaType" NOT NULL,
27 | "postId" TEXT NOT NULL,
28 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
29 | "sharedPostId" TEXT,
30 |
31 | CONSTRAINT "media_pkey" PRIMARY KEY ("id")
32 | );
33 |
34 | -- CreateIndex
35 | CREATE UNIQUE INDEX "media_postId_sharedPostId_key" ON "media"("postId", "sharedPostId");
36 |
37 | -- AddForeignKey
38 | ALTER TABLE "posts" ADD CONSTRAINT "posts_sharedFromId_fkey" FOREIGN KEY ("sharedFromId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE CASCADE;
39 |
40 | -- AddForeignKey
41 | ALTER TABLE "media" ADD CONSTRAINT "media_post_media_fkey" FOREIGN KEY ("postId") REFERENCES "posts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
42 |
43 | -- AddForeignKey
44 | ALTER TABLE "media" ADD CONSTRAINT "media_shared_media_fkey" FOREIGN KEY ("sharedPostId") REFERENCES "posts"("id") ON DELETE SET NULL ON UPDATE CASCADE;
45 |
46 | -- AddForeignKey
47 | ALTER TABLE "media" ADD CONSTRAINT "media_postId_fkey" FOREIGN KEY ("postId") REFERENCES "posts"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
48 |
--------------------------------------------------------------------------------
/prisma/migrations/20250410005214_backup/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - You are about to drop the column `sharedContent` on the `posts` table. All the data in the column will be lost.
5 | - You are about to drop the column `sharedFromId` on the `posts` table. All the data in the column will be lost.
6 | - You are about to drop the `media` table. If the table is not empty, all the data it contains will be lost.
7 |
8 | */
9 | -- DropForeignKey
10 | ALTER TABLE "media" DROP CONSTRAINT "media_postId_fkey";
11 |
12 | -- DropForeignKey
13 | ALTER TABLE "media" DROP CONSTRAINT "media_post_media_fkey";
14 |
15 | -- DropForeignKey
16 | ALTER TABLE "media" DROP CONSTRAINT "media_shared_media_fkey";
17 |
18 | -- DropForeignKey
19 | ALTER TABLE "posts" DROP CONSTRAINT "posts_sharedFromId_fkey";
20 |
21 | -- AlterTable
22 | ALTER TABLE "posts" DROP COLUMN "sharedContent",
23 | DROP COLUMN "sharedFromId";
24 |
25 | -- DropTable
26 | DROP TABLE "media";
27 |
28 | -- CreateTable
29 | CREATE TABLE "Media" (
30 | "id" TEXT NOT NULL,
31 | "url" TEXT NOT NULL,
32 | "type" "MediaType" NOT NULL,
33 | "postId" TEXT NOT NULL,
34 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
35 |
36 | CONSTRAINT "Media_pkey" PRIMARY KEY ("id")
37 | );
38 |
39 | -- AddForeignKey
40 | ALTER TABLE "Media" ADD CONSTRAINT "Media_postId_fkey" FOREIGN KEY ("postId") REFERENCES "posts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
41 |
--------------------------------------------------------------------------------
/prisma/migrations/20250410014100_working/migration.sql:
--------------------------------------------------------------------------------
1 | -- DropIndex
2 | DROP INDEX "user_nerdAt_key";
3 |
--------------------------------------------------------------------------------
/prisma/migrations/20250411135652_add_shared_post_model/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE "shared_posts" (
3 | "id" TEXT NOT NULL,
4 | "postId" TEXT NOT NULL,
5 | "userId" TEXT NOT NULL,
6 | "sharedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
7 |
8 | CONSTRAINT "shared_posts_pkey" PRIMARY KEY ("id")
9 | );
10 |
11 | -- AddForeignKey
12 | ALTER TABLE "shared_posts" ADD CONSTRAINT "shared_posts_postId_fkey" FOREIGN KEY ("postId") REFERENCES "posts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
13 |
14 | -- AddForeignKey
15 | ALTER TABLE "shared_posts" ADD CONSTRAINT "shared_posts_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
16 |
--------------------------------------------------------------------------------
/prisma/migrations/20250411140858_reverse/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - You are about to drop the `shared_posts` table. If the table is not empty, all the data it contains will be lost.
5 |
6 | */
7 | -- DropForeignKey
8 | ALTER TABLE "shared_posts" DROP CONSTRAINT "shared_posts_postId_fkey";
9 |
10 | -- DropForeignKey
11 | ALTER TABLE "shared_posts" DROP CONSTRAINT "shared_posts_userId_fkey";
12 |
13 | -- DropTable
14 | DROP TABLE "shared_posts";
15 |
--------------------------------------------------------------------------------
/prisma/migrations/20250411141355_add_additional_context_to_report/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "reports" ADD COLUMN "additionalContext" TEXT;
3 |
--------------------------------------------------------------------------------
/prisma/migrations/20250419062030_added_bug_report/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateEnum
2 | CREATE TYPE "bugReportSeverity" AS ENUM ('LOW', 'MEDIUM', 'HIGH');
3 |
4 | -- CreateEnum
5 | CREATE TYPE "bugReportStatus" AS ENUM ('PENDING', 'RESOLVED', 'REJECTED');
6 |
7 | -- CreateTable
8 | CREATE TABLE "bugReport" (
9 | "id" TEXT NOT NULL,
10 | "userId" TEXT NOT NULL,
11 | "content" TEXT NOT NULL,
12 | "bugseverity" "bugReportSeverity" NOT NULL DEFAULT 'LOW',
13 | "status" "bugReportStatus" NOT NULL DEFAULT 'PENDING',
14 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
15 | "updatedAt" TIMESTAMP(3) NOT NULL,
16 |
17 | CONSTRAINT "bugReport_pkey" PRIMARY KEY ("id")
18 | );
19 |
20 | -- AddForeignKey
21 | ALTER TABLE "bugReport" ADD CONSTRAINT "bugReport_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
22 |
--------------------------------------------------------------------------------
/prisma/migrations/20250421124920_added_notification/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateEnum
2 | CREATE TYPE "notificationType" AS ENUM ('LIKE', 'COMMENT', 'FOLLOW');
3 |
4 | -- CreateTable
5 | CREATE TABLE "notification" (
6 | "id" TEXT NOT NULL,
7 | "userId" TEXT NOT NULL,
8 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
9 | "type" "notificationType" NOT NULL,
10 | "postId" TEXT,
11 | "commentId" TEXT,
12 | "followerId" TEXT,
13 | "updatedAt" TIMESTAMP(3) NOT NULL,
14 |
15 | CONSTRAINT "notification_pkey" PRIMARY KEY ("id")
16 | );
17 |
18 | -- AddForeignKey
19 | ALTER TABLE "notification" ADD CONSTRAINT "notification_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
20 |
21 | -- AddForeignKey
22 | ALTER TABLE "notification" ADD CONSTRAINT "notification_postId_fkey" FOREIGN KEY ("postId") REFERENCES "posts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
23 |
24 | -- AddForeignKey
25 | ALTER TABLE "notification" ADD CONSTRAINT "notification_commentId_fkey" FOREIGN KEY ("commentId") REFERENCES "postcomments"("id") ON DELETE CASCADE ON UPDATE CASCADE;
26 |
27 | -- AddForeignKey
28 | ALTER TABLE "notification" ADD CONSTRAINT "notification_followerId_fkey" FOREIGN KEY ("followerId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
29 |
--------------------------------------------------------------------------------
/prisma/migrations/20250421153152_add_notifications/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - You are about to drop the `notification` table. If the table is not empty, all the data it contains will be lost.
5 |
6 | */
7 | -- CreateEnum
8 | CREATE TYPE "NotificationType" AS ENUM ('POST_LIKE', 'POST_COMMENT', 'NEW_FOLLOWER', 'PROJECT_UPDATE', 'PROJECT_STAR', 'COMMUNITY_INVITE');
9 |
10 | -- DropForeignKey
11 | ALTER TABLE "notification" DROP CONSTRAINT "notification_commentId_fkey";
12 |
13 | -- DropForeignKey
14 | ALTER TABLE "notification" DROP CONSTRAINT "notification_followerId_fkey";
15 |
16 | -- DropForeignKey
17 | ALTER TABLE "notification" DROP CONSTRAINT "notification_postId_fkey";
18 |
19 | -- DropForeignKey
20 | ALTER TABLE "notification" DROP CONSTRAINT "notification_userId_fkey";
21 |
22 | -- DropTable
23 | DROP TABLE "notification";
24 |
25 | -- DropEnum
26 | DROP TYPE "notificationType";
27 |
28 | -- CreateTable
29 | CREATE TABLE "notifications" (
30 | "id" TEXT NOT NULL,
31 | "userId" TEXT NOT NULL,
32 | "type" "NotificationType" NOT NULL,
33 | "read" BOOLEAN NOT NULL DEFAULT false,
34 | "postId" TEXT,
35 | "commentId" TEXT,
36 | "followerId" TEXT,
37 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
38 | "updatedAt" TIMESTAMP(3) NOT NULL,
39 |
40 | CONSTRAINT "notifications_pkey" PRIMARY KEY ("id")
41 | );
42 |
43 | -- AddForeignKey
44 | ALTER TABLE "notifications" ADD CONSTRAINT "notifications_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
45 |
--------------------------------------------------------------------------------
/prisma/migrations/20250422072157_updare/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - You are about to drop the `notifications` table. If the table is not empty, all the data it contains will be lost.
5 |
6 | */
7 | -- DropForeignKey
8 | ALTER TABLE "notifications" DROP CONSTRAINT "notifications_userId_fkey";
9 |
10 | -- DropTable
11 | DROP TABLE "notifications";
12 |
13 | -- DropEnum
14 | DROP TYPE "NotificationType";
15 |
--------------------------------------------------------------------------------
/prisma/migrations/20250422090303_notification/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateEnum
2 | CREATE TYPE "NotificationType" AS ENUM ('FOLLOW', 'POST_LIKE', 'POST_COMMENT', 'PROJECT_UPDATE', 'PROJECT_STAR', 'PROJECT_RATING', 'PROJECT_REVIEW', 'COMMUNITY_INVITE', 'COMMUNITY_POST', 'PROJECT_FOLLOW', 'MENTION');
3 |
4 | -- CreateTable
5 | CREATE TABLE "notifications" (
6 | "id" TEXT NOT NULL,
7 | "type" "NotificationType" NOT NULL,
8 | "userId" TEXT NOT NULL,
9 | "actorId" TEXT,
10 | "read" BOOLEAN NOT NULL DEFAULT false,
11 | "postId" TEXT,
12 | "commentId" TEXT,
13 | "projectId" TEXT,
14 | "communityId" TEXT,
15 | "message" TEXT NOT NULL,
16 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
17 | "updatedAt" TIMESTAMP(3) NOT NULL,
18 |
19 | CONSTRAINT "notifications_pkey" PRIMARY KEY ("id")
20 | );
21 |
22 | -- CreateIndex
23 | CREATE INDEX "notifications_userId_read_createdAt_idx" ON "notifications"("userId", "read", "createdAt" DESC);
24 |
25 | -- AddForeignKey
26 | ALTER TABLE "notifications" ADD CONSTRAINT "notifications_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
27 |
28 | -- AddForeignKey
29 | ALTER TABLE "notifications" ADD CONSTRAINT "notifications_actorId_fkey" FOREIGN KEY ("actorId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE CASCADE;
30 |
31 | -- AddForeignKey
32 | ALTER TABLE "notifications" ADD CONSTRAINT "notifications_postId_fkey" FOREIGN KEY ("postId") REFERENCES "posts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
33 |
34 | -- AddForeignKey
35 | ALTER TABLE "notifications" ADD CONSTRAINT "notifications_commentId_fkey" FOREIGN KEY ("commentId") REFERENCES "postcomments"("id") ON DELETE CASCADE ON UPDATE CASCADE;
36 |
37 | -- AddForeignKey
38 | ALTER TABLE "notifications" ADD CONSTRAINT "notifications_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
39 |
40 | -- AddForeignKey
41 | ALTER TABLE "notifications" ADD CONSTRAINT "notifications_communityId_fkey" FOREIGN KEY ("communityId") REFERENCES "communities"("id") ON DELETE CASCADE ON UPDATE CASCADE;
42 |
--------------------------------------------------------------------------------
/prisma/migrations/20250422093720_update_nerdspace/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE "update" (
3 | "id" TEXT NOT NULL,
4 | "title" TEXT NOT NULL,
5 | "content" TEXT NOT NULL,
6 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
7 | "updatedAt" TIMESTAMP(3) NOT NULL,
8 |
9 | CONSTRAINT "update_pkey" PRIMARY KEY ("id")
10 | );
11 |
--------------------------------------------------------------------------------
/prisma/migrations/migration_lock.toml:
--------------------------------------------------------------------------------
1 | # Please do not edit this file manually
2 | # It should be added in your version-control system (e.g., Git)
3 | provider = "postgresql"
--------------------------------------------------------------------------------
/public/01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/01.png
--------------------------------------------------------------------------------
/public/03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/03.png
--------------------------------------------------------------------------------
/public/04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/04.png
--------------------------------------------------------------------------------
/public/05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/05.png
--------------------------------------------------------------------------------
/public/Elon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/Elon.png
--------------------------------------------------------------------------------
/public/Enistine.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/Enistine.png
--------------------------------------------------------------------------------
/public/adawa.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/adawa.webp
--------------------------------------------------------------------------------
/public/art.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/art.jpg
--------------------------------------------------------------------------------
/public/auth-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/auth-bg.png
--------------------------------------------------------------------------------
/public/bg9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/bg9.jpg
--------------------------------------------------------------------------------
/public/build.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/build.jpg
--------------------------------------------------------------------------------
/public/fashion.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/fashion.jpg
--------------------------------------------------------------------------------
/public/file.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/fonts/ITCGaramondStd-BkCond.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/fonts/ITCGaramondStd-BkCond.ttf
--------------------------------------------------------------------------------
/public/fonts/ITCGaramondStd-LtCond.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/fonts/ITCGaramondStd-LtCond.ttf
--------------------------------------------------------------------------------
/public/fonts/ITCGaramondStd-LtCondIta.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/fonts/ITCGaramondStd-LtCondIta.ttf
--------------------------------------------------------------------------------
/public/fonts/user.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/fonts/user.jpg
--------------------------------------------------------------------------------
/public/game.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/game.jpg
--------------------------------------------------------------------------------
/public/globe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/gwax.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/gwax.jpg
--------------------------------------------------------------------------------
/public/hu.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/hu.webp
--------------------------------------------------------------------------------
/public/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/logo.jpg
--------------------------------------------------------------------------------
/public/music.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/music.jpg
--------------------------------------------------------------------------------
/public/nerd.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/nerd.jpg
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/obsession.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/obsession.jpg
--------------------------------------------------------------------------------
/public/robot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/robot.jpg
--------------------------------------------------------------------------------
/public/user.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/user.jpg
--------------------------------------------------------------------------------
/public/user_placeholder.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/public/user_placeholder.jpg
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/window.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/(app)/ai/layout.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const ProfileLayout = ({ children }: { children: React.ReactNode }) => {
4 | return <>{children}>;
5 | };
6 |
7 | export default ProfileLayout;
8 |
--------------------------------------------------------------------------------
/src/app/(app)/ai/page.tsx:
--------------------------------------------------------------------------------
1 | import LeftNavbar from "@/components/navbar/left-navbar";
2 | import MobileNavBar from "@/components/navbar/mobile-nav-bar";
3 | import SoonComponent from "@/components/soon/soon";
4 |
5 | const Project = () => {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | );
15 | };
16 |
17 | export default Project;
18 |
--------------------------------------------------------------------------------
/src/app/(app)/community/layout.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const ProfileLayout = ({ children }: { children: React.ReactNode }) => {
4 | return <>{children}>;
5 | };
6 |
7 | export default ProfileLayout;
8 |
--------------------------------------------------------------------------------
/src/app/(app)/community/page.tsx:
--------------------------------------------------------------------------------
1 | import LeftNavbar from "@/components/navbar/left-navbar";
2 | import MobileNavBar from "@/components/navbar/mobile-nav-bar";
3 | import SoonComponent from "@/components/soon/soon";
4 | import React from "react";
5 |
6 | const Project = () => {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | );
17 | };
18 |
19 | export default Project;
20 |
--------------------------------------------------------------------------------
/src/app/(app)/event/layout.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const ProfileLayout = ({ children }: { children: React.ReactNode }) => {
4 | return <>{children}>;
5 | };
6 |
7 | export default ProfileLayout;
8 |
--------------------------------------------------------------------------------
/src/app/(app)/event/page.tsx:
--------------------------------------------------------------------------------
1 | import LeftNavbar from "@/components/navbar/left-navbar";
2 | import MobileNavBar from "@/components/navbar/mobile-nav-bar";
3 | import SoonComponent from "@/components/soon/soon";
4 |
5 | const Project = () => {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | );
16 | };
17 |
18 | export default Project;
19 |
--------------------------------------------------------------------------------
/src/app/(app)/explore/layout.tsx:
--------------------------------------------------------------------------------
1 | const ProfileLayout = ({ children }: { children: React.ReactNode }) => {
2 | return <>{children}>;
3 | };
4 |
5 | export default ProfileLayout;
6 |
--------------------------------------------------------------------------------
/src/app/(app)/explore/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import ExploreEntry from "@/components/explore/explore-entry";
4 | import LeftNavbar from "@/components/navbar/left-navbar";
5 | import MobileNavBar from "@/components/navbar/mobile-nav-bar";
6 | import { useSearchParams } from "next/navigation";
7 | import { useEffect, Suspense } from "react";
8 | import useSearchStore from "@/store/search.store";
9 |
10 | const ExploreContent = () => {
11 | const searchParams = useSearchParams();
12 | const { setQuery } = useSearchStore();
13 |
14 | useEffect(() => {
15 | if (searchParams) {
16 | const query = searchParams.get("q");
17 | if (query) {
18 | setQuery(query);
19 | }
20 | }
21 | }, [searchParams, setQuery]);
22 |
23 | return (
24 |
35 | );
36 | };
37 |
38 | const Explore = () => {
39 | return (
40 | Loading...}>
41 |
42 |
43 | );
44 | };
45 |
46 | export default Explore;
47 |
--------------------------------------------------------------------------------
/src/app/(app)/layout.tsx:
--------------------------------------------------------------------------------
1 | import Navbar from "@/components/navbar";
2 | import React from "react";
3 | import { PostHogProvider } from "@/lib/posthog-provider";
4 |
5 | const AppLayout = ({ children }: { children: React.ReactNode }) => {
6 | return (
7 |
8 |
9 | {/*
13 |
17 |
*/}
21 |
22 |
{children}
23 |
24 |
25 | );
26 | };
27 |
28 | export default AppLayout;
29 |
--------------------------------------------------------------------------------
/src/app/(app)/page.tsx:
--------------------------------------------------------------------------------
1 | import LeftNavbar from "@/components/navbar/left-navbar";
2 | import MobileNavBar from "@/components/navbar/mobile-nav-bar";
3 | import RightNavbar from "@/components/navbar/right-navbar";
4 | import PostInput from "@/components/post/post-input";
5 | import RenderPOst from "@/components/post/render-post";
6 |
7 | export default function Home() {
8 | return (
9 |
18 | );
19 | }
20 | // console.log the current url
--------------------------------------------------------------------------------
/src/app/(app)/profile/[userId]/page.tsx:
--------------------------------------------------------------------------------
1 | // This is a Next.js server component for profile pages
2 | "use client";
3 |
4 | import { use } from "react";
5 |
6 | export default function ProfilePage({
7 | params,
8 | }: {
9 | params: Promise<{ userId: string }>;
10 | }) {
11 | const { userId } = use(params);
12 |
13 | return (
14 |
15 |
Profile {userId}
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/src/app/(app)/profile/layout.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const ProfileLayout = ({ children }: { children: React.ReactNode }) => {
4 | return <>{children}>;
5 | };
6 |
7 | export default ProfileLayout;
8 |
--------------------------------------------------------------------------------
/src/app/(app)/profile/page.tsx:
--------------------------------------------------------------------------------
1 | import LeftNavbar from "@/components/navbar/left-navbar";
2 | import MobileNavBar from "@/components/navbar/mobile-nav-bar";
3 | import ProfilePage from "@/components/settings/profile";
4 |
5 | export default function Profile() {
6 | return (
7 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/(app)/profile/user/[userId]/followers/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import LeftNavbar from "@/components/navbar/left-navbar";
4 | import MobileNavBar from "@/components/navbar/mobile-nav-bar";
5 | import FollowersFollowingList from "@/components/settings/followers-following-list";
6 | import { use } from "react";
7 |
8 | //
9 | export default function FollowersPage({
10 | params,
11 | }: {
12 | params: Promise<{ userId: string }>;
13 | }) {
14 | const { userId } = use(params);
15 |
16 | return (
17 |
18 |
19 |
20 |
21 |
Followers
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | }
30 |
--------------------------------------------------------------------------------
/src/app/(app)/profile/user/[userId]/following/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import LeftNavbar from "@/components/navbar/left-navbar";
4 | import MobileNavBar from "@/components/navbar/mobile-nav-bar";
5 | import FollowersFollowingList from "@/components/settings/followers-following-list";
6 | import { use } from "react";
7 |
8 | export default function FollowingPage({
9 | params,
10 | }: {
11 | params: Promise<{ userId: string }>;
12 | }) {
13 | const { userId } = use(params);
14 |
15 | return (
16 |
17 |
18 |
19 |
20 |
Following
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/src/app/(app)/project/[id]/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import LeftNavbar from "@/components/navbar/left-navbar";
4 | import { use } from "react";
5 | import ProjectDetail from "@/components/project/project-detail";
6 |
7 | export default function ProjectDetailPage({
8 | params,
9 | }: {
10 | params: Promise<{ id: string }>;
11 | }) {
12 | const { id } = use(params);
13 |
14 | return (
15 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/src/app/(app)/project/layout.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const ProfileLayout = ({ children }: { children: React.ReactNode }) => {
4 | return <>{children}>;
5 | };
6 |
7 | export default ProfileLayout;
8 |
--------------------------------------------------------------------------------
/src/app/(app)/project/page.tsx:
--------------------------------------------------------------------------------
1 | import LeftNavbar from "@/components/navbar/left-navbar";
2 | import MobileNavBar from "@/components/navbar/mobile-nav-bar";
3 | import ProjectsPage from "@/components/project/project-component";
4 |
5 | const Project = () => {
6 | return (
7 |
18 | );
19 | };
20 |
21 | export default Project;
22 |
--------------------------------------------------------------------------------
/src/app/(app)/sandbox/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { FileUploader } from "@/components/media/media-uploader";
4 | import React from "react";
5 |
6 | const SandBox = () => {
7 | return (
8 |
9 |
10 | {
14 | console.log("Selected files:", files);
15 |
16 |
17 | }}
18 | />
19 |
20 |
21 | );
22 | };
23 |
24 | export default SandBox;
25 |
--------------------------------------------------------------------------------
/src/app/(app)/user-profile/layout.tsx:
--------------------------------------------------------------------------------
1 | import MobileNavBar from "@/components/navbar/mobile-nav-bar";
2 | import LeftNavbar from "@/components/navbar/left-navbar";
3 | import React from "react";
4 |
5 | const ProfileLayout = ({ children }: { children: React.ReactNode }) => {
6 | return (
7 |
8 |
9 | {children}
10 |
11 |
12 | );
13 | };
14 |
15 | export default ProfileLayout;
16 |
--------------------------------------------------------------------------------
/src/app/(auth)/layout.tsx:
--------------------------------------------------------------------------------
1 | import { authClient } from "@/lib/auth-client";
2 | import { redirect } from "next/navigation";
3 |
4 | const AuthLayout = async ({ children }: { children: React.ReactNode }) => {
5 | const session = await authClient.getSession();
6 |
7 | if (session.data?.user) {
8 | redirect("/");
9 | }
10 |
11 | return {children}
;
12 | };
13 |
14 | export default AuthLayout;
15 |
--------------------------------------------------------------------------------
/src/app/(auth)/reset-password/page.tsx:
--------------------------------------------------------------------------------
1 | import { GalleryVerticalEnd } from "lucide-react";
2 |
3 | import { ResetPasswordFrom } from "@/components/reset-password";
4 | import Image from "next/image";
5 | import { Suspense } from "react";
6 |
7 | export default function ResetPasswordPage() {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
Loading... }>
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | Start your journey here
26 |
27 | @
28 |
29 | Build one simple profile and let our AI work it's magic.
30 | We'll automatically apply to hundreds of jobs for you. Focus on
31 | what matters most - your skills and experience.
32 |
33 |
40 |
41 | Yeabsra Ashebir
42 |
43 |
Developer
44 |
45 |
46 |
47 | );
48 | }
49 |
--------------------------------------------------------------------------------
/src/app/(auth)/signup/page.tsx:
--------------------------------------------------------------------------------
1 | import { GalleryVerticalEnd } from "lucide-react";
2 |
3 | import { SignUpForm } from "@/components/signup-form";
4 | import Image from "next/image";
5 |
6 | export default function SignUpPage() {
7 | return (
8 |
9 |
19 |
20 |
21 |
22 | Start your{" "}
23 | Journey {" "}
24 | here
25 |
26 |
27 |
28 | Create a simple profile and connect with like-minded people who
29 | share your passions. Explore communities, collaborate on projects,
30 | and grow with others who truly get you.
31 |
32 |
33 |
40 |
41 |
42 | Yeabsra Ashebir
43 |
44 |
Developer
45 |
46 |
47 |
48 | );
49 | }
50 |
--------------------------------------------------------------------------------
/src/app/api/auth/[...all]/route.ts:
--------------------------------------------------------------------------------
1 | import { auth } from "@/lib/auth"; // path to your auth file
2 | import { toNextJsHandler } from "better-auth/next-js";
3 |
4 | export const { POST, GET } = toNextJsHandler(auth);
5 |
--------------------------------------------------------------------------------
/src/app/api/dummy/route.ts:
--------------------------------------------------------------------------------
1 | import { generateUsers } from "@/script/dummy-users";
2 | import { NextRequest, NextResponse } from "next/server";
3 |
4 | const users = generateUsers(100);
5 |
6 | export const GET = async (request: NextRequest) => {
7 | try {
8 | const { searchParams } = new URL(request.url);
9 | const page = parseInt(searchParams.get("page") || "1");
10 | const pageSize = parseInt(searchParams.get("pageSize") || "10");
11 |
12 | const start = (page - 1) * pageSize;
13 | const end = start + pageSize;
14 |
15 | const data = users.slice(start, end);
16 |
17 | return NextResponse.json(
18 | {
19 | data: data,
20 | totalPage: Math.ceil(users.length / pageSize),
21 | previouspage: page - 1,
22 | nextpage: page + 1,
23 | },
24 | { status: 200 },
25 | );
26 | } catch (error) {
27 | return NextResponse.json({ error: error }, { status: 500 });
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/src/app/api/media-upload/route.ts:
--------------------------------------------------------------------------------
1 | import getUserSession from "@/functions/get-user";
2 | import { prisma } from "@/lib/prisma";
3 | import { NextRequest, NextResponse } from "next/server";
4 | import { z } from "zod";
5 |
6 | const MediaSchema = z.object({
7 | postId: z.string(),
8 | url: z.string().url(),
9 | mediaType: z.enum(["IMAGE", "VIDEO", "GIF"]),
10 | });
11 |
12 | type MediaType = z.infer;
13 |
14 | export const POST = async (req: NextRequest) => {
15 | try {
16 | const session = await getUserSession();
17 | const body: MediaType = await req.json();
18 |
19 | if (!session) {
20 | return NextResponse.json(
21 | { message: "unauthorized | not logged in" },
22 | { status: 400 },
23 | );
24 | }
25 |
26 | const validationValue = MediaSchema.safeParse(body);
27 | if (!validationValue.success || validationValue.error) {
28 | return NextResponse.json(
29 | { message: "validation error", error: validationValue.error.message },
30 | { status: 401 },
31 | );
32 | }
33 |
34 | const newMedia = await prisma.media.create({
35 | data: {
36 | postId: body.postId,
37 | url: body.url,
38 | type: body.mediaType,
39 | },
40 | });
41 |
42 | return NextResponse.json(
43 | {
44 | data: newMedia,
45 | message: "Media uploaded successfully",
46 | },
47 | { status: 201 },
48 | );
49 | } catch (e) {
50 | return NextResponse.json({ error: e }, { status: 500 });
51 | }
52 | };
53 |
--------------------------------------------------------------------------------
/src/app/api/post/[id]/route.ts:
--------------------------------------------------------------------------------
1 | import { prisma } from "@/lib/prisma";
2 | import { NextRequest, NextResponse } from "next/server";
3 |
4 | export const GET = async (request: NextRequest) => {
5 | try {
6 | const id = request.nextUrl.pathname.split("/").pop() as string;
7 |
8 | const post = await prisma.post.findUnique({
9 | where: { id },
10 | include: {
11 | user: {
12 | include: {
13 | following: true,
14 | },
15 | },
16 | likes: true,
17 | bookmarks: true,
18 | media: true,
19 | postcomments: {
20 | include: {
21 | user: true,
22 | replies: {
23 | include: {
24 | user: true,
25 | },
26 | },
27 | },
28 | },
29 | project: {
30 | include: {
31 | _count: true,
32 | },
33 | },
34 | _count: {
35 | select: {
36 | likes: true,
37 | bookmarks: true,
38 | postcomments: true,
39 | },
40 | },
41 | },
42 | });
43 |
44 | if (!post) {
45 | return NextResponse.json({ message: "Post not found" }, { status: 404 });
46 | }
47 |
48 | const modifiedPost = {
49 | ...post,
50 | user: {
51 | ...post.user,
52 | isFollowingAuthor: post.user.following.length > 0,
53 | },
54 | };
55 |
56 | return NextResponse.json(
57 | {
58 | data: modifiedPost,
59 | message: "Post fetched successfully",
60 | },
61 | { status: 200 },
62 | );
63 | } catch (error) {
64 | console.error("Error fetching post:", error);
65 | return NextResponse.json(
66 | { error: "Internal server error" },
67 | { status: 500 },
68 | );
69 | }
70 | };
71 |
--------------------------------------------------------------------------------
/src/app/api/post/bookmark/route.ts:
--------------------------------------------------------------------------------
1 | import getUserSession from "@/functions/get-user";
2 | import { prisma } from "@/lib/prisma";
3 | import bookmarkSchema from "@/validation/bookmark.validation";
4 | import { NextResponse } from "next/server";
5 |
6 | export const POST = async (req: Request) => {
7 | try {
8 | const session = await getUserSession();
9 |
10 | if (!session) {
11 | return NextResponse.json(
12 | {
13 | message: "unauthorized | not logged in",
14 | },
15 | { status: 400 },
16 | );
17 | }
18 |
19 | const body = await req.json();
20 | const result = bookmarkSchema.safeParse(body);
21 |
22 | if (!result.success) {
23 | return NextResponse.json(
24 | {
25 | message: "Invalid request body",
26 | errors: result.error.errors,
27 | },
28 | { status: 400 },
29 | );
30 | }
31 |
32 | const { userId, postId } = result.data;
33 |
34 | const existingBookmark = await prisma.bookmark.findFirst({
35 | where: { userId, postId },
36 | });
37 |
38 | if (existingBookmark) {
39 | await prisma.bookmark.delete({ where: { id: existingBookmark.id } });
40 | return NextResponse.json(
41 | { message: "Bookmark removed" },
42 | { status: 200 },
43 | );
44 | }
45 |
46 | await prisma.bookmark.create({
47 | data: { userId, postId },
48 | });
49 |
50 | return NextResponse.json({ message: "Bookmarked post" }, { status: 201 });
51 | } catch (err) {
52 | console.error("Error updating comment:", err);
53 | return NextResponse.json({ error: "error" }, { status: 500 });
54 | }
55 | };
56 |
--------------------------------------------------------------------------------
/src/app/api/project/[id]/updates/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from "next/server";
2 | import { prisma } from "@/lib/prisma";
3 | import getUserSession from "@/functions/get-user";
4 |
5 | export async function GET(req: NextRequest) {
6 | try {
7 | const session = await getUserSession();
8 | if (!session) {
9 | return NextResponse.json(
10 | { message: "Unauthorized | Not logged in" },
11 | { status: 401 },
12 | );
13 | }
14 |
15 | const url = new URL(req.url);
16 | const pathParts = url.pathname.split("/").filter((part) => part !== "");
17 | const id = pathParts[pathParts.length - 1]; // Assuming the last part is the id
18 |
19 | const cursor = req.nextUrl.searchParams.get("cursor");
20 | const limit = parseInt(req.nextUrl.searchParams.get("limit") || "3", 10); // Default to 3 updates per page
21 |
22 | const updates = await prisma.projectUpdate.findMany({
23 | where: { projectId: id },
24 | take: limit,
25 | skip: cursor ? 1 : 0,
26 | cursor: cursor ? { id: cursor } : undefined,
27 | orderBy: { createdAt: "desc" },
28 | include: { likes: true, comments: true },
29 | });
30 |
31 | const nextCursor =
32 | updates.length === limit ? updates[updates.length - 1].id : null;
33 |
34 | return NextResponse.json(
35 | {
36 | data: updates,
37 | nextCursor, // Include nextCursor in the response
38 | },
39 | { status: 200 },
40 | );
41 | } catch (error: any) {
42 | console.error("Error fetching project updates:", error);
43 | return NextResponse.json(
44 | { message: "Internal server error" },
45 | { status: 500 },
46 | );
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/app/api/project/rank/route.ts:
--------------------------------------------------------------------------------
1 | import getUserSession from "@/functions/get-user";
2 | import { prisma } from "@/lib/prisma";
3 | import { NextRequest, NextResponse } from "next/server";
4 |
5 | export const GET = async (req: NextRequest) => {
6 | try {
7 | const session = await getUserSession();
8 | if (!session) {
9 | return NextResponse.json(
10 | { message: "unauthorized | not logged in" },
11 | { status: 400 },
12 | );
13 | }
14 |
15 | const limit = parseInt(req.nextUrl.searchParams.get("limit") || "5", 10);
16 | const cursor = req.nextUrl.searchParams.get("cursor");
17 |
18 | const projects = await prisma.project.findMany({
19 | take: limit,
20 | skip: cursor ? 1 : 0,
21 | cursor: cursor ? { id: cursor } : undefined,
22 | orderBy: {
23 | stars: {
24 | _count: "desc",
25 | },
26 | },
27 | include: {
28 | _count: {
29 | select: {
30 | stars: true,
31 | followers: true,
32 | updates: true,
33 | reviews: true,
34 | },
35 | },
36 | },
37 | });
38 |
39 | const nextCursor =
40 | projects.length === limit ? projects[projects.length - 1].id : null;
41 |
42 | return NextResponse.json({
43 | projects: projects.map((project) => ({
44 | id: project.id,
45 | name: project.name,
46 | description: project.description,
47 | stars: project._count.stars,
48 | image: project.image,
49 | follower: project._count.followers,
50 | review: project._count.reviews,
51 | update: project._count.updates,
52 | })),
53 | nextCursor,
54 | });
55 | } catch (error) {
56 | console.error("Error fetching project rankings:", error);
57 | return NextResponse.json({ error: "error" }, { status: 500 });
58 | }
59 | };
60 |
--------------------------------------------------------------------------------
/src/app/api/project/recommendation/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from "next/server";
2 | import { prisma } from "@/lib/prisma";
3 | import getUserSession from "@/functions/get-user";
4 |
5 | export async function GET() {
6 | try {
7 | const session = await getUserSession();
8 | if (!session) {
9 | return NextResponse.json(
10 | { message: "unauthorized | not logged in" },
11 | { status: 401 },
12 | );
13 | }
14 |
15 | const projects = await prisma.project.findMany({
16 | where: {
17 | AND: [
18 | {
19 | userId: {
20 | not: session.user.id,
21 | },
22 | },
23 | {
24 | followers: {
25 | none: {
26 | userId: session.user.id,
27 | },
28 | },
29 | },
30 | ],
31 | },
32 | take: 8,
33 | orderBy: { createdAt: "desc" },
34 | include: {
35 | followers: true,
36 | },
37 | });
38 |
39 | const transformedProjects = projects.map((project) => ({
40 | id: project.id,
41 | name: project.name,
42 | description: project.description,
43 | image: project.image || "/placeholder-image.jpg",
44 | status: project.status,
45 | category: project.category || [],
46 | members: project.followers.length,
47 | }));
48 |
49 | return NextResponse.json({ projects: transformedProjects });
50 | } catch (error) {
51 | console.error("Error fetching projects:", error);
52 | return NextResponse.json(
53 | { error: "Failed to fetch projects" },
54 | { status: 500 },
55 | );
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/app/api/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from "next/server";
2 |
3 | export async function GET() {
4 | return NextResponse.json({ message: "Hello from the API!" });
5 | }
6 |
--------------------------------------------------------------------------------
/src/app/api/security/route.ts:
--------------------------------------------------------------------------------
1 | import getUserSession from "@/functions/get-user";
2 | import { prisma } from "@/lib/prisma";
3 | import { NextRequest } from "next/server";
4 |
5 | export const PATCH = async (req: NextRequest) => {
6 | try {
7 | const { id }: { id: string } = await req.json();
8 | const session = await getUserSession();
9 |
10 | if (!session) {
11 | return Response.json(
12 | {
13 | message: "unauthorized | not logged in",
14 | },
15 | { status: 400 },
16 | );
17 | }
18 |
19 | const post = await prisma.post.findUnique({
20 | where: {
21 | id: id,
22 | userId: session.user.id,
23 | },
24 | });
25 |
26 | if (!post) {
27 | return Response.json(
28 | {
29 | message: "Post not found or you do not have access to it",
30 | },
31 | { status: 404 },
32 | );
33 | }
34 |
35 | const updatedAccess = await prisma.post.update({
36 | where: {
37 | id: id,
38 | },
39 | data: {
40 | access: post.access === "public" ? "private" : "public",
41 | },
42 | });
43 |
44 | return Response.json(updatedAccess, { status: 200 });
45 | } catch (error) {
46 | return Response.json(
47 | {
48 | message: "An error occurred",
49 | },
50 | { status: 500 },
51 | );
52 | }
53 | };
54 |
--------------------------------------------------------------------------------
/src/app/api/user/route.ts:
--------------------------------------------------------------------------------
1 | import getUserSession from "@/functions/get-user";
2 | import { prisma } from "@/lib/prisma";
3 | import { NextRequest, NextResponse } from "next/server";
4 |
5 | export const GET = async (req: NextRequest) => {
6 | try {
7 | const session = await getUserSession();
8 | const userId = await req.nextUrl.searchParams.get("userId");
9 |
10 | if (!userId) {
11 | return NextResponse.json(
12 | {
13 | message: "userId is required",
14 | },
15 | { status: 400 },
16 | );
17 | }
18 |
19 | if (!session) {
20 | return Response.json(
21 | {
22 | message: "unauthorized | not logged in",
23 | },
24 | { status: 400 },
25 | );
26 | }
27 |
28 | const user = await prisma.user.findUnique({
29 | where: {
30 | id: userId,
31 | },
32 |
33 | include: {
34 | posts: true,
35 | country: true,
36 | followers: true,
37 | following: true,
38 | },
39 | });
40 |
41 | if (!user) {
42 | return NextResponse.json(
43 | {
44 | message: "User not found",
45 | },
46 | { status: 404 },
47 | );
48 | }
49 |
50 | return NextResponse.json({
51 | data: user,
52 | });
53 | } catch (error) {
54 | console.error("Error fetching posts:", error);
55 | return NextResponse.json({ error: "error" }, { status: 500 });
56 | }
57 | };
58 |
--------------------------------------------------------------------------------
/src/app/api/users/[userId]/counts/route.ts:
--------------------------------------------------------------------------------
1 | import { prisma } from "@/lib/prisma";
2 | import { NextRequest, NextResponse } from "next/server";
3 |
4 | export async function GET(request: NextRequest) {
5 | try {
6 | const url = new URL(request.url);
7 | const pathParts = url.pathname.split("/").filter((part) => part !== "");
8 | const userId = pathParts[pathParts.length - 1];
9 |
10 | console.log("Fetching counts for userId:", userId);
11 |
12 | if (!userId) {
13 | return NextResponse.json(
14 | {
15 | message: "User ID is required",
16 | },
17 | { status: 400 },
18 | );
19 | }
20 |
21 | // Debug: Check all follows for this user
22 | const allFollows = await prisma.follows.findMany({
23 | where: {
24 | OR: [{ followerId: userId }, { followingId: userId }],
25 | },
26 | });
27 | console.log("All follows for user:", allFollows);
28 |
29 | const [followersCount, followingCount] = await Promise.all([
30 | prisma.follows.count({
31 | where: {
32 | followingId: userId,
33 | },
34 | }),
35 | prisma.follows.count({
36 | where: {
37 | followerId: userId,
38 | },
39 | }),
40 | ]);
41 |
42 | console.log("Counts result:", { followersCount, followingCount });
43 |
44 | return NextResponse.json({
45 | followers: followersCount,
46 | following: followingCount,
47 | });
48 | } catch (error) {
49 | console.error("Error fetching follow counts:", error);
50 | return NextResponse.json(
51 | { error: "Failed to fetch follow counts" },
52 | { status: 500 },
53 | );
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/app/api/users/[userId]/route.ts:
--------------------------------------------------------------------------------
1 | import { prisma } from "@/lib/prisma";
2 | import { NextRequest, NextResponse } from "next/server";
3 |
4 | export async function GET(request: NextRequest) {
5 | try {
6 | const url = new URL(request.url);
7 | const pathParts = url.pathname.split("/").filter((part) => part !== "");
8 | const userId = pathParts[pathParts.length - 1];
9 |
10 | if (!userId) {
11 | return NextResponse.json(
12 | {
13 | message: "User ID is required",
14 | },
15 | { status: 400 },
16 | );
17 | }
18 |
19 | const user = await prisma.user.findUnique({
20 | where: { id: userId },
21 | include: {
22 | _count: {
23 | select: {
24 | followers: true,
25 | following: true,
26 | },
27 | },
28 | country: true,
29 | },
30 | });
31 |
32 | if (!user) {
33 | return NextResponse.json(
34 | {
35 | message: "User not found",
36 | },
37 | { status: 404 },
38 | );
39 | }
40 |
41 | return NextResponse.json({
42 | data: user,
43 | });
44 | } catch (error) {
45 | console.error("Error fetching user:", error);
46 | return NextResponse.json(
47 | { error: "Failed to fetch user" },
48 | { status: 500 },
49 | );
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/app/api/users/check-follow/route.ts:
--------------------------------------------------------------------------------
1 | import getUserSession from "@/functions/get-user";
2 | import { prisma } from "@/lib/prisma";
3 | import { NextResponse } from "next/server";
4 |
5 | export async function GET(request: Request) {
6 | try {
7 | const session = await getUserSession();
8 | if (!session?.user) {
9 | return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
10 | }
11 |
12 | const { searchParams } = new URL(request.url);
13 | const userIds = searchParams.get("userIds")?.split(",");
14 |
15 | if (!userIds || userIds.length === 0) {
16 | return NextResponse.json(
17 | { error: "User IDs are required" },
18 | { status: 400 },
19 | );
20 | }
21 |
22 | const follows = await prisma.follows.findMany({
23 | where: {
24 | followerId: session.user.id,
25 | followingId: {
26 | in: userIds,
27 | },
28 | },
29 | select: {
30 | followingId: true,
31 | },
32 | });
33 |
34 | const followStatus = userIds.reduce(
35 | (acc, userId) => {
36 | acc[userId] = follows.some((follow) => follow.followingId === userId);
37 | return acc;
38 | },
39 | {} as Record,
40 | );
41 |
42 | return NextResponse.json(followStatus);
43 | } catch (error) {
44 | console.error("Error checking follow status:", error);
45 | return NextResponse.json(
46 | { error: "Internal server error" },
47 | { status: 500 },
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/app/api/users/projects/owned/route.ts:
--------------------------------------------------------------------------------
1 | import getUserSession from "@/functions/get-user";
2 | import { prisma } from "@/lib/prisma";
3 | import { NextRequest, NextResponse } from "next/server";
4 |
5 | export async function GET(request: NextRequest) {
6 | try {
7 | const session = await getUserSession();
8 | const cursor = request.nextUrl.searchParams.get("cursor");
9 | const limit = parseInt(request.nextUrl.searchParams.get("limit") || "10");
10 |
11 | if (!session) {
12 | return NextResponse.json(
13 | {
14 | message: "unauthorized | not logged in",
15 | },
16 | { status: 400 },
17 | );
18 | }
19 |
20 | const [ownedProjects, total] = await Promise.all([
21 | prisma.project.findMany({
22 | where: {
23 | userId: session.user.id,
24 | ...(cursor && {
25 | id: {
26 | lt: cursor,
27 | },
28 | }),
29 | },
30 | include: {
31 | user: {
32 | select: {
33 | id: true,
34 | name: true,
35 | image: true,
36 | },
37 | },
38 | },
39 | orderBy: {
40 | id: "desc",
41 | },
42 | take: limit + 1,
43 | }),
44 | prisma.project.count({
45 | where: {
46 | userId: session.user.id,
47 | },
48 | }),
49 | ]);
50 |
51 | const hasNextPage = ownedProjects.length > limit;
52 | const items = hasNextPage ? ownedProjects.slice(0, -1) : ownedProjects;
53 | const nextCursor = hasNextPage ? items[items.length - 1].id : null;
54 |
55 | return NextResponse.json({
56 | data: items,
57 | pagination: {
58 | nextCursor,
59 | hasNextPage,
60 | total,
61 | },
62 | });
63 | } catch (error) {
64 | console.error("Error fetching owned projects:", error);
65 | return NextResponse.json(
66 | { error: "Failed to fetch owned projects" },
67 | { status: 500 },
68 | );
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/app/api/users/route.ts:
--------------------------------------------------------------------------------
1 | import getUserSession from "@/functions/get-user";
2 | import { prisma } from "@/lib/prisma";
3 | import { NextResponse } from "next/server";
4 |
5 | export async function GET(request: Request) {
6 | try {
7 | const session = await getUserSession();
8 |
9 | if (!session) {
10 | return NextResponse.json(
11 | {
12 | message: "unauthorized | not logged in",
13 | },
14 | { status: 400 },
15 | );
16 | }
17 | const { searchParams } = new URL(request.url);
18 | const exclude = searchParams.get("exclude");
19 | const limit = parseInt(searchParams.get("limit") || "5");
20 |
21 | const users = await prisma.user.findMany({
22 | where: exclude
23 | ? {
24 | NOT: { id: exclude },
25 | }
26 | : undefined,
27 | take: limit,
28 | });
29 |
30 | return NextResponse.json(users);
31 | } catch (error) {
32 | console.error("Error fetching users:", error);
33 | return NextResponse.json(
34 | { error: "Failed to fetch users" },
35 | { status: 500 },
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/app/api/whoami/post/route.ts:
--------------------------------------------------------------------------------
1 | import getUserSession from "@/functions/get-user";
2 | import { prisma } from "@/lib/prisma";
3 | import { NextRequest, NextResponse } from "next/server";
4 |
5 | export async function GET(request: NextRequest) {
6 | try {
7 | const session = await getUserSession();
8 |
9 | if (!session) {
10 | return Response.json(
11 | {
12 | message: "unauthorized | not logged in",
13 | },
14 | { status: 400 },
15 | );
16 | }
17 | const cursor = request.nextUrl.searchParams.get("cursor") || null;
18 | const limit = 5;
19 |
20 | const posts = await prisma.post.findMany({
21 | where: {
22 | userId: session.user.id,
23 | access: "public",
24 | },
25 | include: {
26 | user: true,
27 | media: true,
28 | },
29 | orderBy: { createdAt: "desc" },
30 | take: limit,
31 | skip: cursor ? 1 : 0,
32 | cursor: cursor ? { id: cursor } : undefined,
33 | });
34 |
35 | const nextCursor = posts.length > 0 ? posts[posts.length - 1].id : null;
36 |
37 | return NextResponse.json(
38 | {
39 | data: posts,
40 | nextCursor: nextCursor,
41 | message: "posts fetched successfully",
42 | },
43 | { status: 200 },
44 | );
45 | } catch (error) {
46 | console.error("Error fetching posts:", error);
47 | return NextResponse.json({ error: "error" }, { status: 500 });
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/app/api/whoami/private/route.ts:
--------------------------------------------------------------------------------
1 | import getUserSession from "@/functions/get-user";
2 | import { prisma } from "@/lib/prisma";
3 | import { NextRequest, NextResponse } from "next/server";
4 |
5 | export async function GET(request: NextRequest) {
6 | try {
7 | const session = await getUserSession();
8 |
9 | if (!session) {
10 | return Response.json(
11 | {
12 | message: "unauthorized | not logged in",
13 | },
14 | { status: 400 },
15 | );
16 | }
17 | const cursor = request.nextUrl.searchParams.get("cursor") || null;
18 | const limit = 5;
19 |
20 | const posts = await prisma.post.findMany({
21 | where: {
22 | userId: session.user.id,
23 | access: "private",
24 | },
25 | include: {
26 | user: true,
27 | media: true,
28 | },
29 | orderBy: { createdAt: "desc" },
30 | take: limit,
31 | skip: cursor ? 1 : 0,
32 | cursor: cursor ? { id: cursor } : undefined,
33 | });
34 |
35 | const nextCursor = posts.length > 0 ? posts[posts.length - 1].id : null;
36 |
37 | return NextResponse.json(
38 | {
39 | data: posts,
40 | nextCursor: nextCursor,
41 | message: "posts fetched successfully",
42 | },
43 | { status: 200 },
44 | );
45 | } catch (error) {
46 | console.error("Error fetching posts:", error);
47 | return NextResponse.json({ error: "error" }, { status: 500 });
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/app/api/whoami/route.ts:
--------------------------------------------------------------------------------
1 | import getUserSession from "@/functions/get-user";
2 | import { prisma } from "@/lib/prisma";
3 | import { NextRequest, NextResponse } from "next/server";
4 |
5 | export const GET = async (req: NextRequest) => {
6 | try {
7 | const session = await getUserSession();
8 |
9 | if (!session) {
10 | return Response.json(
11 | {
12 | message: "unauthorized | not logged in",
13 | },
14 | { status: 400 },
15 | );
16 | }
17 |
18 | const user = await prisma.user.findUnique({
19 | where: {
20 | id: session.user.id,
21 | },
22 |
23 | include: {
24 | posts: true,
25 | country: true,
26 | _count: {
27 | select: {
28 | following: true,
29 | followers: true,
30 | },
31 | },
32 | },
33 | });
34 |
35 | return NextResponse.json({
36 | data: user,
37 | });
38 | } catch (error) {
39 | console.error("Error fetching posts:", error);
40 | return NextResponse.json({ error: "error" }, { status: 500 });
41 | }
42 | };
43 |
--------------------------------------------------------------------------------
/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/src/app/favicon.ico
--------------------------------------------------------------------------------
/src/app/onboarding/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useEffect } from "react";
4 | import { useRouter } from "next/navigation";
5 | import { authClient } from "@/lib/auth-client";
6 | import { OnboardingForm } from "@/components/onboarding-form";
7 |
8 | export default function OnboardingPage() {
9 | const router = useRouter();
10 | const session = authClient.useSession();
11 |
12 | return (
13 |
14 |
15 |
16 |
17 | Welcome to{" "}
18 |
19 | Nerdspace
20 |
21 |
22 |
23 | Let's set up your profile to help you connect with like-minded
24 | people
25 |
26 |
27 |
28 |
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/src/app/post/[id]/layout.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from "react";
2 |
3 | const PostExploreLayout = ({ children }: { children: ReactNode }) => {
4 | return {children}
;
5 | };
6 |
7 | export default PostExploreLayout;
8 |
--------------------------------------------------------------------------------
/src/app/projects/recommendations/layout.tsx:
--------------------------------------------------------------------------------
1 | import Navbar from "@/components/navbar";
2 | import React from "react";
3 |
4 | const AppLayout = ({ children }: { children: React.ReactNode }) => {
5 | return (
6 |
10 | );
11 | };
12 |
13 | export default AppLayout;
14 |
--------------------------------------------------------------------------------
/src/app/settings/layout.tsx:
--------------------------------------------------------------------------------
1 | import Navbar from "@/components/navbar";
2 | import React from "react";
3 |
4 | const ProfileLayout = ({ children }: { children: React.ReactNode }) => {
5 | return (
6 |
10 | );
11 | };
12 |
13 | export default ProfileLayout;
14 |
--------------------------------------------------------------------------------
/src/app/settings/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import SettingsScreen from "@/components/app-sidebar";
4 | import { SidebarProvider } from "@/components/ui/sidebar";
5 | import { useSearchParams } from "next/navigation";
6 | import { Suspense } from "react";
7 |
8 | const ProfileScreenContent = () => {
9 | const searchParams = useSearchParams();
10 | const tab = searchParams?.get("tab") || "profile";
11 |
12 | return (
13 |
14 |
15 |
16 |
17 |
18 | );
19 | };
20 |
21 | const ProfileScreen = () => {
22 | return (
23 | Loading...}>
24 |
25 |
26 | );
27 | };
28 |
29 | export default ProfileScreen;
30 |
--------------------------------------------------------------------------------
/src/components/UserListSkeleton.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const UserListSkeleton = () => {
4 | // Create an array of 6 items to render skeleton cards
5 | const skeletonItems = Array.from({ length: 6 }, (_, i) => i);
6 |
7 | return (
8 |
9 | {skeletonItems.map((item) => (
10 |
15 |
16 |
19 |
20 |
21 |
22 |
23 |
27 |
28 |
29 |
30 |
31 | ))}
32 |
33 | );
34 | };
35 |
36 | export default UserListSkeleton;
37 |
--------------------------------------------------------------------------------
/src/components/custom/image-upload.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import axios from "axios";
3 | import { Input } from "@/components/ui/input";
4 | import Image from "next/image";
5 |
6 | interface ImageUploadProps {
7 | onUpload: (imageUrl: string) => void;
8 | }
9 |
10 | export default function ImageUploader({ onUpload }: ImageUploadProps) {
11 | const cloudinaryUploadUrl = process.env.NEXT_PUBLIC_CLOUDINARY_UPLOAD_URL!;
12 | const cloudinaryUploadPreset = process.env.NEXT_PUBLIC_CLOUDINARY_UPLOAD_PRESET!;
13 |
14 | const [previewUrl, setPreviewUrl] = useState(null);
15 |
16 | const handleFileChange = async (e: React.ChangeEvent) => {
17 | if (e.target.files && e.target.files[0]) {
18 | const file = e.target.files[0];
19 |
20 | // Generate a local preview
21 | const reader = new FileReader();
22 | reader.onload = () => {
23 | setPreviewUrl(reader.result as string);
24 | };
25 | reader.readAsDataURL(file);
26 |
27 | // Upload the image
28 | const formData = new FormData();
29 | formData.append("file", file);
30 | formData.append("upload_preset", cloudinaryUploadPreset);
31 |
32 | try {
33 | const response = await axios.post(cloudinaryUploadUrl, formData);
34 | onUpload(response.data.secure_url);
35 | } catch (error) {
36 | console.error("Image upload failed:", error);
37 | }
38 | }
39 | };
40 |
41 | return (
42 |
43 |
44 | {previewUrl && (
45 |
46 |
47 |
48 | )}
49 |
50 | );
51 | }
--------------------------------------------------------------------------------
/src/components/follow-button.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Button } from "@/components/ui/button";
4 | import { followService } from "@/functions/follow";
5 | import { useState } from "react";
6 | import { toast } from "sonner";
7 |
8 | interface FollowButtonProps {
9 | userId: string;
10 | isFollowing: boolean;
11 | onFollowChange?: (isFollowing: boolean) => void;
12 | }
13 |
14 | export function FollowButton({ userId, isFollowing, onFollowChange }: FollowButtonProps) {
15 | const [isLoading, setIsLoading] = useState(false);
16 |
17 | const handleFollow = async () => {
18 | try {
19 | setIsLoading(true);
20 | const action = isFollowing ? "unfollow" : "follow";
21 | await followService.followUser(userId, action);
22 | onFollowChange?.(!isFollowing);
23 | toast.success(`Successfully ${action}ed user`);
24 | } catch (error) {
25 | if (error instanceof Error) {
26 | if (error.message.includes("already following")) {
27 | // If we're already following, try to unfollow instead
28 | await followService.followUser(userId, "unfollow");
29 | onFollowChange?.(false);
30 | toast.success("Successfully unfollowed user");
31 | return;
32 | }
33 | toast.error(error.message);
34 | } else {
35 | toast.error("Failed to follow user");
36 | }
37 | } finally {
38 | setIsLoading(false);
39 | }
40 | };
41 |
42 | return (
43 |
49 | {isLoading ? "Loading..." : isFollowing ? "Following" : "Follow"}
50 |
51 | );
52 | }
--------------------------------------------------------------------------------
/src/components/nav-secondary.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { type LucideIcon } from "lucide-react"
3 |
4 | import {
5 | SidebarGroup,
6 | SidebarGroupContent,
7 | SidebarMenu,
8 | SidebarMenuButton,
9 | SidebarMenuItem,
10 | } from "@/components/ui/sidebar"
11 |
12 | export function NavSecondary({
13 | items,
14 | ...props
15 | }: {
16 | items: {
17 | title: string
18 | url: string
19 | icon: LucideIcon
20 | }[]
21 | } & React.ComponentPropsWithoutRef) {
22 | return (
23 |
24 |
25 |
26 | {items.map((item) => (
27 |
28 |
29 |
30 |
31 | {item.title}
32 |
33 |
34 |
35 | ))}
36 |
37 |
38 |
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/src/components/navbar/right-navbar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useEffect, useState } from "react";
4 | import { authClient } from "@/lib/auth-client";
5 | import { prisma } from "@/lib/prisma";
6 | import FollowList from "../user/followList";
7 | import RecomendedProjects from "../user/recommend-project";
8 |
9 | const RightNavbar = () => {
10 | const [users, setUsers] = useState([]);
11 | const [loading, setLoading] = useState(true);
12 |
13 | useEffect(() => {
14 | const fetchUsers = async () => {
15 | try {
16 | const user = await authClient.getSession();
17 | const response = await fetch(`/api/users?exclude=${user?.data?.user.id}&limit=5`);
18 | const data = await response.json();
19 | setUsers(data);
20 | } catch (error) {
21 | console.error("Error fetching users:", error);
22 | } finally {
23 | setLoading(false);
24 | }
25 | };
26 |
27 | fetchUsers();
28 | }, []);
29 |
30 | return (
31 |
37 | );
38 | };
39 |
40 | export default RightNavbar;
41 |
--------------------------------------------------------------------------------
/src/components/post/comment/DeleteCommentModal.tsx:
--------------------------------------------------------------------------------
1 | import { useMutation, useQueryClient } from "@tanstack/react-query";
2 | import axios from "axios";
3 | import { Button } from "@/components/ui/button";
4 | import toast from "react-hot-toast";
5 | import {
6 | Dialog,
7 | DialogContent,
8 | DialogHeader,
9 | DialogTitle,
10 | DialogDescription,
11 | DialogFooter,
12 | } from "@/components/ui/dialog";
13 |
14 | interface DeleteCommentModalProps {
15 | commentId: string;
16 | isOpen: boolean;
17 | onClose: () => void;
18 | }
19 |
20 | // TODO: Add a confirmation modal
21 | const DeleteCommentModal: React.FC = ({
22 | commentId,
23 | isOpen,
24 | onClose,
25 |
26 | }) => {
27 | const queryClient = useQueryClient();
28 |
29 | const mutation = useMutation({
30 | mutationFn: async () => {
31 | await axios.delete(`/api/post/comment?commentId=${commentId}`);
32 | },
33 | onSuccess: () => {
34 | queryClient.invalidateQueries({ queryKey: ["comment"] });
35 | toast.success("Comment deleted successfully");
36 | onClose();
37 | },
38 | onError: () => {
39 | toast.error("Error occurred while deleting comment");
40 | },
41 | });
42 |
43 | const handleDelete = () => {
44 | mutation.mutate();
45 | };
46 |
47 | return (
48 |
49 |
50 |
51 | Delete Comment
52 |
53 | Are you sure you want to delete this comment?
54 |
55 |
56 |
57 |
58 | Cancel
59 |
60 |
65 | Delete
66 |
67 |
68 |
69 |
70 | );
71 | };
72 |
73 | export default DeleteCommentModal;
74 |
--------------------------------------------------------------------------------
/src/components/post/comment/comment.tsx:
--------------------------------------------------------------------------------
1 | import CommentSkeleton from "@/components/skeleton/comment.skelton";
2 | import { Button } from "@/components/ui/button";
3 | import { SendIcon } from "lucide-react";
4 | import React, { JSX } from "react";
5 | import PostCommentInterface from "@/interface/auth/comment.interface";
6 |
7 | interface CommentComponentProps {
8 | commentContent: string;
9 | setCommentContent: React.Dispatch>;
10 | handleCommentSubmit: () => void;
11 | commentLoading: boolean;
12 | renderComments: (comments: PostCommentInterface[], parentId: string | null, level?: number) => JSX.Element[];
13 | comment: PostCommentInterface[];
14 | }
15 |
16 | const CommentComponent: React.FC = ({
17 | commentContent,
18 | setCommentContent,
19 | handleCommentSubmit,
20 | commentLoading,
21 | renderComments,
22 | comment,
23 | }) => {
24 | return (
25 |
26 |
27 |
28 | setCommentContent(e.target.value)}
33 | />
34 |
38 |
39 |
40 |
41 | {commentLoading &&
}
42 |
{renderComments(comment, null)}
43 |
44 | );
45 | };
46 |
47 | export default CommentComponent;
48 |
--------------------------------------------------------------------------------
/src/components/quality-notice.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Bell, Info, ImagePlus } from "lucide-react";
4 | import { useState } from "react";
5 | import { Button } from "./ui/button";
6 |
7 | const QualityNotice = () => {
8 | const [showQualityTip, setShowQualityTip] = useState(false);
9 |
10 | return (
11 | setShowQualityTip(true)}
14 | onMouseLeave={() => setShowQualityTip(false)}
15 | >
16 |
21 |
22 |
23 | !
24 |
25 |
26 |
27 | {showQualityTip && (
28 |
29 |
30 |
31 |
32 |
Quality Guidelines
33 |
34 | Please ensure your images are high-quality and relevant to maintain our community standards 📸✨
35 |
36 |
37 |
38 |
39 | )}
40 |
41 | );
42 | };
43 |
44 | export default QualityNotice;
--------------------------------------------------------------------------------
/src/components/search-form.tsx:
--------------------------------------------------------------------------------
1 | import { Search } from "lucide-react"
2 |
3 | import { Label } from "@/components/ui/label"
4 | import { SidebarInput } from "@/components/ui/sidebar"
5 |
6 | export function SearchForm({ ...props }: React.ComponentProps<"form">) {
7 | return (
8 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/settings/notification-setting.tsx:
--------------------------------------------------------------------------------
1 | const NotificationSetting = () => {
2 | return (
3 |
4 | Notification Setting
5 |
6 | )
7 | }
8 |
9 | export default NotificationSetting;
10 |
--------------------------------------------------------------------------------
/src/components/settings/tabs/CollectionsTab.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 |
3 | export default function CollectionsTab() {
4 | return (
5 |
6 | {Array.from({ length: 2 }).map((_, i) => (
7 |
8 |
9 | {Array.from({ length: 4 }).map((_, j) => (
10 |
11 |
18 |
19 | ))}
20 |
21 |
22 |
Collection {i + 1}
23 |
{4} items
24 |
25 |
26 | ))}
27 |
28 | );
29 | }
30 |
--------------------------------------------------------------------------------
/src/components/settings/tabs/PrivateTab.tsx:
--------------------------------------------------------------------------------
1 | import RenderMyPrivatePost from "../my-private-post";
2 |
3 | export default function PrivateTab() {
4 | return (
5 |
6 |
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/sidebar/nav-main.tsx:
--------------------------------------------------------------------------------
1 | import type React from "react"
2 | import { ChevronRight } from "lucide-react"
3 | import { SidebarMenu, SidebarMenuButton } from "@/components/ui/sidebar"
4 | import { cn } from "@/lib/utils"
5 |
6 | interface NavMainProps {
7 | items: {
8 | title: string
9 | url: string
10 | icon: React.ReactNode
11 | isActive?: boolean
12 | items?: {
13 | title: string
14 | url: string
15 | }[]
16 | }[]
17 | }
18 |
19 | export function NavMain({ items }: NavMainProps) {
20 | return (
21 |
22 |
23 |
Settings
24 |
25 |
26 | {items.map((item) => (
27 |
28 |
29 |
30 | {item.icon}
31 | {item.title}
32 |
33 | {item.items && item.items.length > 0 && }
34 |
35 |
36 | ))}
37 |
38 |
39 | )
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/src/components/sidebar/nav-projects.tsx:
--------------------------------------------------------------------------------
1 | import type React from "react"
2 | import { Plus } from "lucide-react"
3 | import { SidebarMenu, SidebarMenuButton } from "@/components/ui/sidebar"
4 |
5 | interface NavProjectsProps {
6 | projects: {
7 | name: string
8 | url: string
9 | icon: React.ReactNode
10 | }[]
11 | }
12 |
13 | export function NavProjects({ projects }: NavProjectsProps) {
14 | return (
15 |
16 |
17 |
Communities
18 |
19 |
20 |
21 |
22 |
23 | {projects.map((project) => (
24 |
25 |
26 |
27 | {project.icon}
28 | {project.name}
29 |
30 |
31 |
32 | ))}
33 |
34 |
35 | )
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/src/components/sidebar/nav-secondary.tsx:
--------------------------------------------------------------------------------
1 | import type React from "react"
2 | import { SidebarMenu, SidebarMenuButton } from "@/components/ui/sidebar"
3 |
4 | interface NavSecondaryProps extends React.HTMLAttributes {
5 | items: {
6 | title: string
7 | url: string
8 | icon: React.ReactNode
9 | }[]
10 | }
11 |
12 | export function NavSecondary({ items, className, ...props }: NavSecondaryProps) {
13 | return (
14 |
15 |
16 | {items.map((item) => (
17 |
18 |
19 |
20 | {item.icon}
21 | {item.title}
22 |
23 |
24 |
25 | ))}
26 |
27 |
28 | )
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/src/components/sidebar/nav-user.tsx:
--------------------------------------------------------------------------------
1 | import { ChevronRight } from "lucide-react"
2 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
3 |
4 | interface NavUserProps {
5 | user: {
6 | name: string
7 | email: string
8 | avatar: string
9 | }
10 | }
11 |
12 | export function NavUser({ user }: NavUserProps) {
13 | return (
14 |
15 |
16 |
17 | {user.name.charAt(0)}
18 |
19 |
20 | {user.name}
21 | {user.email}
22 |
23 |
24 |
25 | )
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/src/components/site-header.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { SidebarIcon } from "lucide-react"
4 |
5 | import { SearchForm } from "@/components/search-form"
6 | import {
7 | Breadcrumb,
8 | BreadcrumbItem,
9 | BreadcrumbLink,
10 | BreadcrumbList,
11 | BreadcrumbPage,
12 | BreadcrumbSeparator,
13 | } from "@/components/ui/breadcrumb"
14 | import { Button } from "@/components/ui/button"
15 | import { Separator } from "@/components/ui/separator"
16 | import { useSidebar } from "@/components/ui/sidebar"
17 |
18 | export function SiteHeader() {
19 | const { toggleSidebar } = useSidebar()
20 |
21 | return (
22 |
49 | )
50 | }
51 |
--------------------------------------------------------------------------------
/src/components/skeleton/comment.skelton.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Skeleton } from "../ui/skeleton";
3 |
4 | const CommentSkeleton = () => {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | {/*
*/}
33 |
34 |
35 |
36 |
37 | );
38 | };
39 |
40 | export default CommentSkeleton;
41 |
--------------------------------------------------------------------------------
/src/components/skeleton/morepostFetch.skeleton.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Skeleton } from "../ui/skeleton";
3 | import { Card } from "../ui/card";
4 |
5 | const MorePostsFetchSkeleton = () => {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | );
25 | };
26 |
27 | export default MorePostsFetchSkeleton;
28 |
--------------------------------------------------------------------------------
/src/components/soon/soon.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { ArrowRight } from "lucide-react";
4 | import { useRouter } from "next/navigation";
5 | import { Button } from "../ui/button";
6 | const SoonComponent = () => {
7 | const router = useRouter();
8 |
9 | return (
10 |
11 |
12 | Under Development
13 |
14 |
15 | We're working hard to bring you this feature. Thank you for your
16 | patience while we perfect it.
17 |
18 |
router.push("/")}
22 | >
23 | Return Home
24 |
25 |
26 |
27 | );
28 | };
29 |
30 | export default SoonComponent;
31 |
--------------------------------------------------------------------------------
/src/components/theme-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { ThemeProvider as NextThemesProvider } from "next-themes"
5 |
6 | export function ThemeProvider({
7 | children,
8 | ...props
9 | }: React.ComponentProps) {
10 | return {children}
11 | }
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/components/toaster.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/src/components/toaster.tsx
--------------------------------------------------------------------------------
/src/components/ui/alert.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const alertVariants = cva(
7 | "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
8 | {
9 | variants: {
10 | variant: {
11 | default: "bg-background text-foreground",
12 | destructive:
13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14 | },
15 | },
16 | defaultVariants: {
17 | variant: "default",
18 | },
19 | }
20 | )
21 |
22 | const Alert = React.forwardRef<
23 | HTMLDivElement,
24 | React.HTMLAttributes & VariantProps
25 | >(({ className, variant, ...props }, ref) => (
26 |
32 | ))
33 | Alert.displayName = "Alert"
34 |
35 | const AlertTitle = React.forwardRef<
36 | HTMLParagraphElement,
37 | React.HTMLAttributes
38 | >(({ className, ...props }, ref) => (
39 |
44 | ))
45 | AlertTitle.displayName = "AlertTitle"
46 |
47 | const AlertDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | AlertDescription.displayName = "AlertDescription"
58 |
59 | export { Alert, AlertTitle, AlertDescription }
60 |
--------------------------------------------------------------------------------
/src/components/ui/aspect-ratio.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
4 |
5 | const AspectRatio = AspectRatioPrimitive.Root
6 |
7 | export { AspectRatio }
8 |
--------------------------------------------------------------------------------
/src/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as AvatarPrimitive from "@radix-ui/react-avatar"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Avatar = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 | ))
21 | Avatar.displayName = AvatarPrimitive.Root.displayName
22 |
23 | const AvatarImage = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
32 | ))
33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName
34 |
35 | const AvatarFallback = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 | ))
48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
49 |
50 | export { Avatar, AvatarImage, AvatarFallback }
51 |
--------------------------------------------------------------------------------
/src/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const badgeVariants = cva(
7 | "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8 | {
9 | variants: {
10 | variant: {
11 | default:
12 | "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
13 | secondary:
14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15 | destructive:
16 | "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
17 | outline: "text-foreground",
18 | },
19 | },
20 | defaultVariants: {
21 | variant: "default",
22 | },
23 | }
24 | )
25 |
26 | export interface BadgeProps
27 | extends React.HTMLAttributes,
28 | VariantProps {}
29 |
30 | function Badge({ className, variant, ...props }: BadgeProps) {
31 | return (
32 |
33 | )
34 | }
35 |
36 | export { Badge, badgeVariants }
37 |
--------------------------------------------------------------------------------
/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { Slot } from "@radix-ui/react-slot"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 | import { cn } from "@/lib/utils"
7 |
8 | const buttonVariants = cva(
9 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
10 | {
11 | variants: {
12 | variant: {
13 | default:
14 | "bg-primary text-primary-foreground shadow hover:bg-primary/90",
15 | destructive:
16 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
17 | outline:
18 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
19 | secondary:
20 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
21 | ghost: "hover:bg-accent hover:text-accent-foreground",
22 | link: "text-primary underline-offset-4 hover:underline",
23 | },
24 | size: {
25 | default: "h-9 px-4 py-2",
26 | sm: "h-8 rounded-md px-3 text-xs",
27 | lg: "h-10 rounded-md px-8",
28 | icon: "h-9 w-9",
29 | },
30 | },
31 | defaultVariants: {
32 | variant: "default",
33 | size: "default",
34 | },
35 | }
36 | )
37 |
38 | export interface ButtonProps
39 | extends React.ButtonHTMLAttributes,
40 | VariantProps {
41 | asChild?: boolean
42 | }
43 |
44 | const Button = React.forwardRef(
45 | ({ className, variant, size, asChild = false, ...props }, ref) => {
46 | const Comp = asChild ? Slot : "button"
47 | return (
48 |
53 | )
54 | }
55 | )
56 | Button.displayName = "Button"
57 |
58 | export { Button, buttonVariants }
59 |
--------------------------------------------------------------------------------
/src/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
5 | import { Check } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Checkbox = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => (
13 |
21 |
24 |
25 |
26 |
27 | ))
28 | Checkbox.displayName = CheckboxPrimitive.Root.displayName
29 |
30 | export { Checkbox }
31 |
--------------------------------------------------------------------------------
/src/components/ui/collapsible.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
4 |
5 | const Collapsible = CollapsiblePrimitive.Root
6 |
7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
8 |
9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
10 |
11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }
12 |
--------------------------------------------------------------------------------
/src/components/ui/hover-card.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const HoverCard = HoverCardPrimitive.Root
9 |
10 | const HoverCardTrigger = HoverCardPrimitive.Trigger
11 |
12 | const HoverCardContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16 |
26 | ))
27 | HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
28 |
29 | export { HoverCard, HoverCardTrigger, HoverCardContent }
30 |
--------------------------------------------------------------------------------
/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Input = React.forwardRef>(
6 | ({ className, type, ...props }, ref) => {
7 | return (
8 |
17 | )
18 | }
19 | )
20 | Input.displayName = "Input"
21 |
22 | export { Input }
23 |
--------------------------------------------------------------------------------
/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/src/components/ui/loading-spinner.tsx:
--------------------------------------------------------------------------------
1 | export function LoadingSpinner() {
2 | return (
3 |
6 | )
7 | }
8 |
9 |
--------------------------------------------------------------------------------
/src/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as PopoverPrimitive from "@radix-ui/react-popover"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Popover = PopoverPrimitive.Root
9 |
10 | const PopoverTrigger = PopoverPrimitive.Trigger
11 |
12 | const PopoverAnchor = PopoverPrimitive.Anchor
13 |
14 | const PopoverContent = React.forwardRef<
15 | React.ElementRef,
16 | React.ComponentPropsWithoutRef
17 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
18 |
19 |
29 |
30 | ))
31 | PopoverContent.displayName = PopoverPrimitive.Content.displayName
32 |
33 | export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
34 |
--------------------------------------------------------------------------------
/src/components/ui/progress.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | interface ProgressProps {
4 | value: number;
5 | className?: string;
6 | label?: string;
7 | }
8 |
9 | export const Progress: React.FC = ({
10 | value,
11 | className,
12 | label,
13 | }) => {
14 | return (
15 |
16 |
20 | {label && (
21 |
22 | {label}
23 |
24 | )}
25 |
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/src/components/ui/radio-group.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
5 | import { Circle } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const RadioGroup = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => {
13 | return (
14 |
19 | )
20 | })
21 | RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
22 |
23 | const RadioGroupItem = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => {
27 | return (
28 |
36 |
37 |
38 |
39 |
40 | )
41 | })
42 | RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
43 |
44 | export { RadioGroup, RadioGroupItem }
45 |
--------------------------------------------------------------------------------
/src/components/ui/resizable.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { GripVertical } from "lucide-react"
4 | import * as ResizablePrimitive from "react-resizable-panels"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const ResizablePanelGroup = ({
9 | className,
10 | ...props
11 | }: React.ComponentProps) => (
12 |
19 | )
20 |
21 | const ResizablePanel = ResizablePrimitive.Panel
22 |
23 | const ResizableHandle = ({
24 | withHandle,
25 | className,
26 | ...props
27 | }: React.ComponentProps & {
28 | withHandle?: boolean
29 | }) => (
30 | div]:rotate-90",
33 | className
34 | )}
35 | {...props}
36 | >
37 | {withHandle && (
38 |
39 |
40 |
41 | )}
42 |
43 | )
44 |
45 | export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
46 |
--------------------------------------------------------------------------------
/src/components/ui/scroll-area.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const ScrollArea = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, children, ...props }, ref) => (
12 |
17 |
18 | {children}
19 |
20 |
21 |
22 |
23 | ))
24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
25 |
26 | const ScrollBar = React.forwardRef<
27 | React.ElementRef,
28 | React.ComponentPropsWithoutRef
29 | >(({ className, orientation = "vertical", ...props }, ref) => (
30 |
43 |
44 |
45 | ))
46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
47 |
48 | export { ScrollArea, ScrollBar }
49 |
--------------------------------------------------------------------------------
/src/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = "horizontal", decorative = true, ...props },
14 | ref
15 | ) => (
16 |
27 | )
28 | )
29 | Separator.displayName = SeparatorPrimitive.Root.displayName
30 |
31 | export { Separator }
32 |
--------------------------------------------------------------------------------
/src/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 |
--------------------------------------------------------------------------------
/src/components/ui/slider.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SliderPrimitive from "@radix-ui/react-slider"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Slider = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 |
21 |
22 |
23 |
24 |
25 | ))
26 | Slider.displayName = SliderPrimitive.Root.displayName
27 |
28 | export { Slider }
29 |
--------------------------------------------------------------------------------
/src/components/ui/sonner.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { useTheme } from "next-themes"
4 | import { Toaster as Sonner } from "sonner"
5 |
6 | type ToasterProps = React.ComponentProps
7 |
8 | const Toaster = ({ ...props }: ToasterProps) => {
9 | const { theme = "system" } = useTheme()
10 |
11 | return (
12 |
28 | )
29 | }
30 |
31 | export { Toaster }
32 |
--------------------------------------------------------------------------------
/src/components/ui/switch.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SwitchPrimitives from "@radix-ui/react-switch"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Switch = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 |
25 |
26 | ))
27 | Switch.displayName = SwitchPrimitives.Root.displayName
28 |
29 | export { Switch }
30 |
--------------------------------------------------------------------------------
/src/components/ui/tabs.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TabsPrimitive from "@radix-ui/react-tabs"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Tabs = TabsPrimitive.Root
9 |
10 | const TabsList = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, ...props }, ref) => (
14 |
22 | ))
23 | TabsList.displayName = TabsPrimitive.List.displayName
24 |
25 | const TabsTrigger = React.forwardRef<
26 | React.ElementRef,
27 | React.ComponentPropsWithoutRef
28 | >(({ className, ...props }, ref) => (
29 |
37 | ))
38 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
39 |
40 | const TabsContent = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
52 | ))
53 | TabsContent.displayName = TabsPrimitive.Content.displayName
54 |
55 | export { Tabs, TabsList, TabsTrigger, TabsContent }
56 |
--------------------------------------------------------------------------------
/src/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 |
18 | )
19 | })
20 | Textarea.displayName = "Textarea"
21 |
22 | export { Textarea }
23 |
--------------------------------------------------------------------------------
/src/components/ui/toaster.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { useToast } from "@/hooks/use-toast"
4 | import {
5 | Toast,
6 | ToastClose,
7 | ToastDescription,
8 | ToastProvider,
9 | ToastTitle,
10 | ToastViewport,
11 | } from "@/components/ui/toast"
12 |
13 | export function Toaster() {
14 | const { toasts } = useToast()
15 |
16 | return (
17 |
18 | {toasts.map(function ({ id, title, description, action, ...props }) {
19 | return (
20 |
21 |
22 | {title && {title} }
23 | {description && (
24 | {description}
25 | )}
26 |
27 | {action}
28 |
29 |
30 | )
31 | })}
32 |
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/ui/toggle-group.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
5 | import { type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 | import { toggleVariants } from "@/components/ui/toggle"
9 |
10 | const ToggleGroupContext = React.createContext<
11 | VariantProps
12 | >({
13 | size: "default",
14 | variant: "default",
15 | })
16 |
17 | const ToggleGroup = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef &
20 | VariantProps
21 | >(({ className, variant, size, children, ...props }, ref) => (
22 |
27 |
28 | {children}
29 |
30 |
31 | ))
32 |
33 | ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
34 |
35 | const ToggleGroupItem = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef &
38 | VariantProps
39 | >(({ className, children, variant, size, ...props }, ref) => {
40 | const context = React.useContext(ToggleGroupContext)
41 |
42 | return (
43 |
54 | {children}
55 |
56 | )
57 | })
58 |
59 | ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
60 |
61 | export { ToggleGroup, ToggleGroupItem }
62 |
--------------------------------------------------------------------------------
/src/components/ui/toggle.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TogglePrimitive from "@radix-ui/react-toggle"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const toggleVariants = cva(
10 | "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
11 | {
12 | variants: {
13 | variant: {
14 | default: "bg-transparent",
15 | outline:
16 | "border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground",
17 | },
18 | size: {
19 | default: "h-9 px-2 min-w-9",
20 | sm: "h-8 px-1.5 min-w-8",
21 | lg: "h-10 px-2.5 min-w-10",
22 | },
23 | },
24 | defaultVariants: {
25 | variant: "default",
26 | size: "default",
27 | },
28 | }
29 | )
30 |
31 | const Toggle = React.forwardRef<
32 | React.ElementRef,
33 | React.ComponentPropsWithoutRef &
34 | VariantProps
35 | >(({ className, variant, size, ...props }, ref) => (
36 |
41 | ))
42 |
43 | Toggle.displayName = TogglePrimitive.Root.displayName
44 |
45 | export { Toggle, toggleVariants }
46 |
--------------------------------------------------------------------------------
/src/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const TooltipProvider = TooltipPrimitive.Provider
9 |
10 | const Tooltip = TooltipPrimitive.Root
11 |
12 | const TooltipTrigger = TooltipPrimitive.Trigger
13 |
14 | const TooltipContent = React.forwardRef<
15 | React.ElementRef,
16 | React.ComponentPropsWithoutRef
17 | >(({ className, sideOffset = 4, ...props }, ref) => (
18 |
19 |
28 |
29 | ))
30 | TooltipContent.displayName = TooltipPrimitive.Content.displayName
31 |
32 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
33 |
--------------------------------------------------------------------------------
/src/components/user/FollowListSkeleton.tsx:
--------------------------------------------------------------------------------
1 | import { Skeleton } from "../ui/skeleton";
2 |
3 | const FollowListSkeleton = () => {
4 | return (
5 |
6 |
Who to Follow
7 |
8 | {[...Array(3)].map((_, index) => (
9 |
17 | ))}
18 |
19 |
20 | );
21 | };
22 |
23 | export default FollowListSkeleton;
24 |
25 |
26 | export const ProjectRecommendationSkeleton = () => {
27 | return (
28 |
29 |
Projects to Follow
30 |
31 | {[...Array(3)].map((_, index) => (
32 |
40 | ))}
41 |
42 |
43 | );
44 | };
45 |
46 |
--------------------------------------------------------------------------------
/src/components/user/trending.tsx:
--------------------------------------------------------------------------------
1 | import { unstable_cache } from "next/cache";
2 | import { prisma } from "@/lib/prisma";
3 | import Link from "next/link";
4 | import { formatNumber } from "@/functions/format-number";
5 |
6 | const getTrendingTopics = unstable_cache(
7 | async () => {
8 | const result = await prisma.$queryRaw<{ hashtag: string; count: bigint }[]>`
9 | SELECT LOWER(hashtags[1]) AS hashtag, COUNT(*) AS count
10 | FROM (
11 | SELECT regexp_matches(content, '#[[:alnum:]_]+', 'g') AS hashtags
12 | FROM posts
13 | ) sub
14 | GROUP BY hashtag
15 | ORDER BY count DESC, hashtag ASC
16 | LIMIT 3
17 | `;
18 |
19 | return result.map((row) => ({
20 | hashtag: row.hashtag.startsWith("#") ? row.hashtag : `#${row.hashtag}`,
21 | count: Number(row.count),
22 | }));
23 | },
24 | [`trending_topics_${Math.floor(Date.now() / (3 * 60 * 60 * 1000))}`],
25 | { revalidate: 3 * 60 * 60 }
26 | );
27 |
28 | export async function TrendingTopics() {
29 | const trendingTopics = await getTrendingTopics();
30 |
31 | return (
32 |
33 |
Trending topics
34 | {trendingTopics.map(({ hashtag, count }) => {
35 | const title = hashtag.replace("#", "");
36 |
37 | return (
38 |
39 |
40 | {hashtag}
41 |
42 |
43 | {formatNumber(count)} {count === 1 ? "post" : "posts"}
44 |
45 |
46 | );
47 | })}
48 |
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/src/functions/access-change-post.ts:
--------------------------------------------------------------------------------
1 | import usePostStore from "@/store/post.store";
2 | import axios from "axios";
3 |
4 | const changePostAccess = async () => {
5 | const postState = usePostStore.getState();
6 | const response = await axios.patch("/api/security", {
7 | id: postState.selectedPost?.id,
8 | });
9 |
10 | return response.data;
11 | };
12 |
13 | export default changePostAccess;
14 |
--------------------------------------------------------------------------------
/src/functions/calculate-time-difference.ts:
--------------------------------------------------------------------------------
1 | import TimeAgo from "javascript-time-ago";
2 | import en from "javascript-time-ago/locale/en";
3 | import fr from "javascript-time-ago/locale/fr";
4 |
5 | TimeAgo.addLocale(en);
6 | TimeAgo.addLocale(fr);
7 |
8 | export const timeAgo = (
9 | dateInput: string | Date,
10 | locale: string = "en",
11 | ): string => {
12 | const date = typeof dateInput === "string" ? new Date(dateInput) : dateInput;
13 |
14 | if (!(date instanceof Date) || isNaN(date.getTime())) {
15 | throw new Error("Invalid date");
16 | }
17 |
18 | const timeAgoInstance = new TimeAgo(locale);
19 | return timeAgoInstance.format(date);
20 | };
21 |
--------------------------------------------------------------------------------
/src/functions/create-post.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | const createPost = async (data: { content: string; fileUrls: string[] }) => {
4 | const response = await axios.post("/api/post", data, {
5 | withCredentials: true,
6 | });
7 |
8 | console.log("user sent this", data);
9 | return response.data.data;
10 | };
11 |
12 | export default createPost;
13 |
--------------------------------------------------------------------------------
/src/functions/delete-post.ts:
--------------------------------------------------------------------------------
1 | import usePostStore from "@/store/post.store";
2 | import axios from "axios";
3 |
4 | const deletePostFunction = async () => {
5 | const postState = usePostStore.getState();
6 | const response = await axios.delete("/api/post", {
7 | data: { id: postState.selectedPost?.id },
8 | });
9 |
10 | return response.data;
11 | };
12 |
13 | export default deletePostFunction;
14 |
--------------------------------------------------------------------------------
/src/functions/edit-post.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | const editPostFunction = async (data: { id: string; content: string }) => {
4 | const response = await axios.patch("/api/post", data);
5 | return response.data;
6 | };
7 |
8 | export default editPostFunction;
9 |
--------------------------------------------------------------------------------
/src/functions/fetch-my-post.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | const fetchMyPosts = async ({ pageParam }: { pageParam: string | null }) => {
4 | const response = await axios.get("/api/whoami/post", {
5 | params: { cursor: pageParam },
6 | withCredentials: true,
7 | });
8 | return response.data;
9 | };
10 |
11 | export default fetchMyPosts;
12 |
--------------------------------------------------------------------------------
/src/functions/fetch-my-private-post.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | const fetchMyPrivatePosts = async ({
4 | pageParam,
5 | }: {
6 | pageParam: string | null;
7 | }) => {
8 | const response = await axios.get("/api/whoami/private", {
9 | params: { cursor: pageParam },
10 | withCredentials: true,
11 | });
12 | return response.data;
13 | };
14 |
15 | export default fetchMyPrivatePosts;
16 |
--------------------------------------------------------------------------------
/src/functions/fetch-post.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | const fetchPosts = async ({ pageParam }: { pageParam: string | null }) => {
4 | const response = await axios.get("/api/post", {
5 | params: { cursor: pageParam },
6 | });
7 |
8 | console.log("posts fetached", response.data);
9 | return response.data;
10 | };
11 |
12 | export default fetchPosts;
13 |
--------------------------------------------------------------------------------
/src/functions/fetchProject.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | export const fetchProject = async (id: string) => {
4 | const response = await axios.get(`/api/project/${id}`);
5 | return response.data.data;
6 | };
7 |
--------------------------------------------------------------------------------
/src/functions/format-number.ts:
--------------------------------------------------------------------------------
1 | export function formatNumber(n: number): string {
2 | return Intl.NumberFormat("en-US", {
3 | notation: "compact",
4 | maximumFractionDigits: 1,
5 | }).format(n);
6 | }
7 |
--------------------------------------------------------------------------------
/src/functions/get-notification.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import getUserSession from "./get-user";
3 | import { Notification } from "@/api/notification";
4 |
5 | const getNotifications = async (): Promise => {
6 | const session = await getUserSession();
7 |
8 | if (!session) {
9 | return null;
10 | }
11 |
12 | try {
13 | const response = await axios.get("/api/notification");
14 | return response.data;
15 | } catch (error) {
16 | console.error("Error fetching notifications:", error);
17 | return null;
18 | }
19 | };
20 |
21 | export default getNotifications;
22 |
--------------------------------------------------------------------------------
/src/functions/get-user.ts:
--------------------------------------------------------------------------------
1 | import { auth } from "@/lib/auth";
2 | import { headers } from "next/headers";
3 |
4 | // a function to get users session and authenticates the user -----
5 | const getUserSession = async () => {
6 | const session = await auth.api.getSession({
7 | headers: await headers(),
8 | });
9 |
10 | return session;
11 | };
12 |
13 | export default getUserSession;
14 |
--------------------------------------------------------------------------------
/src/functions/get-who-am-i.ts:
--------------------------------------------------------------------------------
1 | import useUserStore from "@/store/user.store";
2 | import axios from "axios";
3 |
4 | const getWhoAmI = async () => {
5 | const response = await axios.get("/api/whoami", {
6 | withCredentials: true,
7 | });
8 |
9 | useUserStore.setState(() => ({
10 | user: response.data.data,
11 | }));
12 |
13 | return response.data;
14 | };
15 |
16 | export default getWhoAmI;
17 |
--------------------------------------------------------------------------------
/src/functions/like-post.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/src/functions/like-post.ts
--------------------------------------------------------------------------------
/src/functions/render-helper.ts:
--------------------------------------------------------------------------------
1 | const trimLimits = {
2 | sm: 15,
3 | md: 12,
4 | lg: 25,
5 | };
6 |
7 | export const getTrimLimit = () => {
8 | if (window.innerWidth < 640) {
9 | return trimLimits.sm;
10 | } else if (window.innerWidth >= 640 && window.innerWidth < 1024) {
11 | return trimLimits.md;
12 | } else {
13 | return trimLimits.lg;
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/src/hooks/use-debounce.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | export function useDebounce(value: T, delay: number): T {
4 | const [debouncedValue, setDebouncedValue] = useState(value);
5 |
6 | useEffect(() => {
7 | const timer = setTimeout(() => {
8 | setDebouncedValue(value);
9 | }, delay);
10 |
11 | return () => {
12 | clearTimeout(timer);
13 | };
14 | }, [value, delay]);
15 |
16 | return debouncedValue;
17 | }
18 |
--------------------------------------------------------------------------------
/src/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(undefined)
7 |
8 | React.useEffect(() => {
9 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
10 | const onChange = () => {
11 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
12 | }
13 | mql.addEventListener("change", onChange)
14 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
15 | return () => mql.removeEventListener("change", onChange)
16 | }, [])
17 |
18 | return !!isMobile
19 | }
20 |
--------------------------------------------------------------------------------
/src/hooks/useFetchPosts.tsx:
--------------------------------------------------------------------------------
1 | // import { useQuery } from "@tanstack/react-query";
2 | // import axios from "axios";
3 | // import postInterface from "@/interface/auth/post.interface";
4 |
5 | // const useFetchPosts = () => {
6 | // return useQuery({
7 | // queryKey: ["posts"],
8 | // queryFn: async () => {
9 | // const response = await axios.get("/api/post");
10 | // return response.data.data as postInterface[];
11 | // },
12 | // });
13 | // };
14 |
15 | // export default useFetchPosts;
16 |
--------------------------------------------------------------------------------
/src/interface/auth/comment.interface.ts:
--------------------------------------------------------------------------------
1 | import UserInterface from "./user.interface";
2 |
3 | interface PostCommentInterface {
4 | id: string;
5 | userId: string;
6 | postId: string;
7 | parentId?: string | null;
8 | content: string;
9 | replies: PostCommentInterface[]; // Nested replies
10 | createdAt: Date;
11 | user?: UserInterface;
12 | }
13 |
14 | export default PostCommentInterface;
15 |
--------------------------------------------------------------------------------
/src/interface/auth/community.interface.ts:
--------------------------------------------------------------------------------
1 | import PostCommentInterface from "@/interface/auth/comment.interface";
2 | import UserInterface from "@/interface/auth/user.interface";
3 |
4 | export interface CommunityInterface {
5 | id: string;
6 | name: string;
7 | description: string;
8 | image?: string;
9 | createdAt: Date;
10 | updatedAt: Date;
11 | creatorId: string;
12 | creator: UserInterface;
13 | members: CommunityMembership[];
14 | posts: CommunityPost[];
15 | categoryId?: string;
16 | category?: CommunityCategory;
17 | }
18 |
19 | interface CommunityMembership {
20 | id: string;
21 | userId: string;
22 | communityId: string;
23 | role: CommunityRole;
24 | user: UserInterface;
25 | community: CommunityInterface;
26 | joinedAt: Date;
27 | }
28 |
29 | interface CommunityPost {
30 | id: string;
31 | image?: string;
32 | content: string;
33 | createdAt: Date;
34 | updatedAt: Date;
35 | userId: string;
36 | communityId: string;
37 | user: UserInterface;
38 | community: CommunityInterface;
39 | likes: { id: string; postId: string; userId: string }[];
40 | comments: PostCommentInterface[];
41 | }
42 |
43 | interface CommunityCategory {
44 | id: string;
45 | name: string;
46 | communities: CommunityInterface[];
47 | }
48 |
49 | enum CommunityRole {
50 | ADMIN = "ADMIN",
51 | MODERATOR = "MODERATOR",
52 | MEMBER = "MEMBER",
53 | }
54 |
--------------------------------------------------------------------------------
/src/interface/auth/onboarding.interface.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | const CountrySchema = z.object({
4 | alpha2: z.string().length(2),
5 | alpha3: z.string().length(3),
6 | countryCallingCodes: z.array(z.string()),
7 | currencies: z.array(z.string()),
8 | emoji: z.string().optional(),
9 | ioc: z.string(),
10 | languages: z.array(z.string()),
11 | name: z.string(),
12 | status: z.string(),
13 | });
14 |
15 | export const OnboardingSchema = z.object({
16 | country: CountrySchema.optional(),
17 | bio: z.string().optional(),
18 | displayName: z.string().optional(),
19 | nerdAt: z.string().optional(),
20 | image: z.string().url().optional(),
21 | firstTime: z.boolean(),
22 | link: z.string().optional(),
23 | coverImage: z.string().url().optional(),
24 | });
25 |
26 | export type OnboardingType = z.infer;
27 |
--------------------------------------------------------------------------------
/src/interface/auth/post.interface.ts:
--------------------------------------------------------------------------------
1 | import PostCommentInterface from "./comment.interface";
2 | import ProjectInterface from "./project.interface";
3 | import UserInterface from "./user.interface";
4 |
5 | interface postInterface {
6 | id: string;
7 | content: string;
8 | createdAt: Date;
9 | updatedAt: Date | null;
10 | userId: string;
11 | access: postAccess;
12 | shared?: boolean;
13 | likes: { id: string; postId: string; userId: string }[];
14 | bookmarks: { id: string; postId: string; userId: string }[];
15 | user: UserInterface;
16 | media: { id: string; url: string; type: string }[]; // Add media field
17 | replies?: PostCommentInterface[];
18 | project?: ProjectInterface;
19 | _count: {
20 | likes: number;
21 | bookmarks: number;
22 | replies: number;
23 | postcomments: number;
24 | };
25 | }
26 |
27 | export enum postAccess {
28 | private,
29 | public,
30 | }
31 |
32 | export default postInterface;
33 |
--------------------------------------------------------------------------------
/src/interface/auth/project.interface.ts:
--------------------------------------------------------------------------------
1 | import UserInterface from "./user.interface";
2 |
3 | type likeType = {
4 | id: string;
5 | userId: string;
6 | updateId: string;
7 | };
8 |
9 | interface starProject {
10 | id: string;
11 | userId: string;
12 | projectId: string;
13 | }
14 |
15 | export type ProjectFollowers = {
16 | id: string;
17 | userId: string;
18 | projectId: string;
19 | };
20 |
21 | export type reviewProject = {
22 | id: string;
23 | userId: string;
24 | projectId: string;
25 | content: string;
26 | user: UserInterface;
27 | createdAt: string;
28 | };
29 |
30 | export type commentType = {
31 | id: string;
32 | userId: string;
33 | updateId: string;
34 | content: string;
35 | createdAt: string;
36 | updatedAt: string;
37 | user: UserInterface;
38 | };
39 |
40 | export interface UpdateInterface {
41 | id: string;
42 | title: string;
43 | image: string;
44 | content: string;
45 | projectId: string;
46 | createdAt: string;
47 | userId: string;
48 | likes: likeType[];
49 | comments: commentType[];
50 | }
51 |
52 | interface ProjectInterface {
53 | id: string;
54 | name: string;
55 | description: string;
56 | image: string;
57 | userId: string;
58 | status: "COMPLETED" | "IN_PROGRESS" | "DRAFT";
59 | category: string[];
60 | access: "public" | "private";
61 | createdAt: string;
62 | members?: UserInterface[];
63 | tags?: string[];
64 | _count: {
65 | updates: number;
66 | stars: number;
67 | ratings: number;
68 | reviews: number;
69 | followers: number;
70 | };
71 | user: UserInterface;
72 | updates: UpdateInterface[];
73 | stars: starProject[];
74 | followers: ProjectFollowers[];
75 | reviews: reviewProject[];
76 | nextCursor: string;
77 | }
78 |
79 | export interface ProjectInterfaceToSubmit {
80 | name: string;
81 | description: string;
82 | category: string[];
83 | access: "public" | "private";
84 | status: "ONGOING" | "COMPLETED" | "PAUSED" | "CANCELLED";
85 | image: string;
86 | }
87 |
88 | export default ProjectInterface;
89 |
--------------------------------------------------------------------------------
/src/interface/auth/projectInterface:
--------------------------------------------------------------------------------
1 | // import UserInterface from "./user.interface";
2 |
3 | // interface UpdateInterface {
4 | // id: string;
5 | // title: string;
6 | // image: string;
7 | // content: string;
8 | // projectId: string;
9 | // createdAt: string;
10 | // userId: string;
11 | // likes?: number; // Optional likes field
12 | // comments?: number; // Optional comments field
13 | // }
14 |
15 | // export default UpdateInterface;
16 |
--------------------------------------------------------------------------------
/src/interface/auth/signin.interface.ts:
--------------------------------------------------------------------------------
1 | export interface signInInterface {
2 | email: string;
3 | password: string;
4 | }
5 |
--------------------------------------------------------------------------------
/src/interface/auth/signup.interface.ts:
--------------------------------------------------------------------------------
1 | export interface signUpInterface {
2 | email: string;
3 | password: string;
4 | name: string;
5 | image?: string;
6 | }
7 |
--------------------------------------------------------------------------------
/src/interface/auth/user.interface.ts:
--------------------------------------------------------------------------------
1 | import { Country } from "@/components/ui/country-dropdown";
2 | import postInterface from "./post.interface";
3 |
4 | interface UserInterface {
5 | id: string;
6 | email: string;
7 | emailVerified: boolean;
8 | name: string;
9 | createdAt: Date;
10 | updatedAt: Date;
11 | image?: string | null;
12 | nerdAt?: string | null;
13 | bio?: string | null;
14 | link?: string | null;
15 | visualName?: string | null;
16 | firstTime: boolean;
17 | country: Country;
18 | posts: postInterface[];
19 | username: string;
20 | coverImage?: string | null;
21 | isFollowingAuthor?: boolean;
22 | _count: {
23 | followers: number;
24 | following: number;
25 | posts: number;
26 | };
27 | }
28 |
29 | export default UserInterface;
30 |
--------------------------------------------------------------------------------
/src/lib/auth-client.ts:
--------------------------------------------------------------------------------
1 | import { createAuthClient } from "better-auth/react";
2 |
3 | export const authClient = createAuthClient({
4 | baseURL: process.env.NEXT_PUBLIC_AUTH_URL,
5 | cookieOptions: {
6 | secure: process.env.NODE_ENV === "production",
7 | sameSite: "lax",
8 | },
9 |
10 | fetchOptions: {
11 | credentials: "include",
12 | },
13 | });
14 |
15 | export const { signIn, signUp, useSession } = createAuthClient();
16 |
--------------------------------------------------------------------------------
/src/lib/axios.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios"
2 |
3 | // Create an Axios instance with default config
4 | const api = axios.create({
5 | baseURL: "/api",
6 | headers: {
7 | "Content-Type": "application/json",
8 | },
9 | })
10 |
11 | // Add a response interceptor for consistent error handling
12 | api.interceptors.response.use(
13 | (response) => response,
14 | (error) => {
15 | const message = error.response?.data?.message || "An unexpected error occurred"
16 | return Promise.reject(new Error(message))
17 | },
18 | )
19 |
20 | export default api
21 |
22 |
--------------------------------------------------------------------------------
/src/lib/posthog-provider.tsx:
--------------------------------------------------------------------------------
1 | // app/providers.tsx
2 | 'use client'
3 |
4 | import { usePathname, useSearchParams } from "next/navigation"
5 | import { useEffect, Suspense } from "react"
6 | import { usePostHog } from 'posthog-js/react'
7 |
8 | import posthog from 'posthog-js'
9 | import { PostHogProvider as PHProvider } from 'posthog-js/react'
10 |
11 | export function PostHogProvider({ children }: { children: React.ReactNode }) {
12 | useEffect(() => {
13 | posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY as string, {
14 | api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://us.i.posthog.com',
15 | person_profiles: 'identified_only', // or 'always' to create profiles for anonymous users as well
16 | capture_pageview: false // Disable automatic pageview capture, as we capture manually
17 | })
18 | }, [])
19 |
20 | return (
21 |
22 |
23 | {children}
24 |
25 | )
26 | }
27 |
28 | function PostHogPageView() {
29 | const pathname = usePathname()
30 | const searchParams = useSearchParams()
31 | const posthog = usePostHog()
32 |
33 | // Track pageviews
34 | useEffect(() => {
35 | if (pathname && posthog) {
36 | let url = window.origin + pathname
37 | if (searchParams.toString()) {
38 | url = url + "?" + searchParams.toString();
39 | }
40 |
41 | posthog.capture('$pageview', { '$current_url': url })
42 | }
43 | }, [pathname, searchParams, posthog])
44 |
45 | return null
46 | }
47 |
48 | // Wrap PostHogPageView in Suspense to avoid the useSearchParams usage above
49 | // from de-opting the whole app into client-side rendering
50 | // See: https://nextjs.org/docs/messages/deopted-into-client-rendering
51 | function SuspendedPostHogPageView() {
52 | return (
53 |
54 |
55 |
56 | )
57 | }
--------------------------------------------------------------------------------
/src/lib/posthog.ts:
--------------------------------------------------------------------------------
1 | import { PostHog } from "posthog-node"
2 |
3 | export default function PostHogClient() {
4 | const posthogClient = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
5 | host: "https://us.i.posthog.com",
6 | flushAt: 1,
7 | flushInterval: 0,
8 | })
9 | return posthogClient
10 | }
11 |
--------------------------------------------------------------------------------
/src/lib/prisma.ts:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from "@prisma/client";
2 |
3 | const globalForPrisma = globalThis as unknown as {
4 | prisma: PrismaClient | undefined;
5 | };
6 |
7 | export const prisma = globalForPrisma.prisma ?? new PrismaClient();
8 |
9 | if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
10 |
--------------------------------------------------------------------------------
/src/lib/providers.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
4 | import { ReactQueryDevtools } from "@tanstack/react-query-devtools"
5 | import { useState, type ReactNode } from "react"
6 |
7 | interface ProvidersProps {
8 | children: ReactNode
9 | }
10 |
11 | export default function Providers({ children }: ProvidersProps) {
12 | // Create a client for each session to avoid shared state across users
13 | const [queryClient] = useState(
14 | () =>
15 | new QueryClient({
16 | defaultOptions: {
17 | queries: {
18 | staleTime: 60 * 1000, // 1 minute
19 | refetchOnWindowFocus: false,
20 | },
21 | },
22 | }),
23 | )
24 |
25 | return (
26 |
27 | {children}
28 |
29 |
30 | )
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/src/lib/sendEmail.ts:
--------------------------------------------------------------------------------
1 | import nodemailer from "nodemailer";
2 |
3 | interface emailInput {
4 | to: string;
5 | subject: string;
6 | text?: string;
7 | html?: string;
8 | from?: string;
9 | }
10 |
11 | const transporter = nodemailer.createTransport({
12 | host: "smtp.gmail.com",
13 | port: 465,
14 | secure: true,
15 | auth: {
16 | user: process.env.EMAIL_USER,
17 | pass: process.env.EMAIL_PASS,
18 | },
19 | });
20 |
21 | const sendEmail = async ({ to, subject, text, html, from }: emailInput) => {
22 | const info = await transporter.sendMail({
23 | from: from || "NerdSpace Team ",
24 | to: `${to}`,
25 | subject: `${subject}`,
26 | text: text || "",
27 | html: html,
28 | });
29 |
30 | console.log("Message sent: %s", info.messageId);
31 | };
32 |
33 | export default sendEmail;
34 |
--------------------------------------------------------------------------------
/src/lib/types.ts:
--------------------------------------------------------------------------------
1 | export type User = {
2 | id: string;
3 | name: string;
4 | email: string;
5 | username: string;
6 | image?: string;
7 | };
8 |
9 | export type Community = {
10 | id: string;
11 | name: string;
12 | description: string;
13 | image?: string;
14 | createdAt: Date;
15 | updatedAt: Date;
16 | creatorId: string;
17 | members: CommunityMembership[];
18 | posts: CommunityPost[];
19 | categoryId?: string;
20 | };
21 |
22 | export type CommunityMembership = {
23 | id: string;
24 | userId: string;
25 | communityId: string;
26 | role: CommunityRole;
27 | joinedAt: Date;
28 | user?: User;
29 | };
30 |
31 | export type CommunityPost = {
32 | id: string;
33 | image?: string;
34 | content: string;
35 | createdAt: Date;
36 | updatedAt: Date;
37 | userId: string;
38 | communityId: string;
39 | user: {
40 | id: string;
41 | name: string;
42 | username: string;
43 | image?: string;
44 | isFollowing?: boolean;
45 | };
46 | likes: Like[];
47 | comments: PostComment[];
48 | };
49 |
50 | export type CommunityCategory = {
51 | id: string;
52 | name: string;
53 | };
54 |
55 | export type CommunityRole = "ADMIN" | "MODERATOR" | "MEMBER";
56 |
57 | export type Like = {
58 | id: string;
59 | userId: string;
60 | postId: string;
61 | };
62 |
63 | export type PostComment = {
64 | id: string;
65 | content: string;
66 | createdAt: Date;
67 | userId: string;
68 | postId: string;
69 | };
70 |
--------------------------------------------------------------------------------
/src/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 |
--------------------------------------------------------------------------------
/src/lib/validations/notification.ts:
--------------------------------------------------------------------------------
1 | // import { z } from "zod";
2 |
3 | // export const createNotificationSchema = z.object({
4 | // type: z.enum(["LIKE", "COMMENT", "FOLLOW"]),
5 | // postId: z.string().optional(),
6 | // commentId: z.string().optional(),
7 | // followerId: z.string().optional(),
8 | // });
9 |
10 | // export type CreateNotificationInput = z.infer;
11 |
--------------------------------------------------------------------------------
/src/lib/validators/community.ts:
--------------------------------------------------------------------------------
1 | // export type Community = {
2 | // id: string;
3 | // name: string;
4 | // description: string;
5 | // image?: string;
6 | // createdAt: Date;
7 | // updatedAt: Date;
8 | // creatorId: string;
9 | // members: any[]; // Replace 'any' with the correct type if available
10 | // posts: any[]; // Replace 'any' with the correct type if available
11 | // categoryId?: string;
12 | // };
13 |
--------------------------------------------------------------------------------
/src/middleware.ts:
--------------------------------------------------------------------------------
1 | // import { NextRequest, NextResponse } from "next/server";
2 | // import { getSessionCookie } from "better-auth";
3 |
4 | // export async function middleware(request: NextRequest) {
5 | // const sessionCookie = getSessionCookie(request);
6 | // if (!sessionCookie) {
7 | // return NextResponse.redirect(new URL("/login", request.url));
8 | // }
9 | // return NextResponse.next();
10 | // }
11 |
12 | // // const
13 | // export const config = {
14 | // runtime: "experimental-edge",
15 | // matcher: ["/dashboard"],
16 | // };
17 |
18 | import { NextRequest, NextResponse } from "next/server";
19 | import { getSessionCookie } from "better-auth/cookies";
20 | export async function middleware(request: NextRequest) {
21 | const sessionCookie = getSessionCookie(request);
22 | const { pathname } = request.nextUrl;
23 | if (
24 | sessionCookie &&
25 | ["/login", "/signup", "/reset-password", "/forgot-password"].includes(
26 | pathname,
27 | )
28 | ) {
29 | return NextResponse.redirect(new URL("/dashboard", request.url));
30 | }
31 | if (!sessionCookie && pathname.startsWith("/")) {
32 | return NextResponse.redirect(new URL("/login", request.url));
33 | }
34 | return NextResponse.next();
35 | }
36 |
37 | export const config = {
38 | matcher: [
39 | "/",
40 | "/profile",
41 | "/settings",
42 | "/projects",
43 | "/onboarding",
44 | "/pos",
45 | "/ai",
46 | "/community",
47 | "/user-profile",
48 | "/sandbox",
49 | "/whotofollow",
50 | "/explore",
51 | "/event",
52 | "/project",
53 | ],
54 | };
55 |
--------------------------------------------------------------------------------
/src/providers/day-picker-provider.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/src/providers/day-picker-provider.tsx
--------------------------------------------------------------------------------
/src/providers/tanstack-query-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
4 | import { ReactNode } from "react";
5 | export const queryClient = new QueryClient({
6 | defaultOptions: {
7 | queries: {
8 | staleTime: 1000 * 60 * 5, // 5 minutes
9 | gcTime: 1000 * 60 * 30, // 30 minutes
10 | retry: 1,
11 | refetchOnWindowFocus: false,
12 | },
13 | },
14 | });
15 |
16 | const TanstackQueryProvider = ({ children }: { children: ReactNode }) => {
17 | return (
18 |
19 | {children}
20 | {/* */}
21 |
22 | );
23 | };
24 |
25 | export default TanstackQueryProvider;
26 |
--------------------------------------------------------------------------------
/src/providers/who-am-i-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import useUserStore from "@/store/user.store";
4 | import { useQuery } from "@tanstack/react-query";
5 | import axios from "axios";
6 | import { ReactNode } from "react";
7 |
8 | const WhoAmIProvider = ({ children }: { children: ReactNode }) => {
9 | const { setuser, setIsLoading } = useUserStore();
10 |
11 | useQuery({
12 | queryKey: ["whoami"],
13 | queryFn: async () => {
14 | try {
15 | const response = await axios.get("/api/whoami", {
16 | withCredentials: true,
17 | });
18 | setuser(response.data.data);
19 | return response.data;
20 | } catch (error) {
21 | console.error("Failed to fetch user:", error);
22 | // setuser(null);
23 | throw error;
24 | } finally {
25 | setIsLoading(false);
26 | }
27 | },
28 | staleTime: 1000 * 60 * 5, // 5 minutes
29 | retry: false,
30 | });
31 |
32 | return <>{children}>;
33 | };
34 |
35 | export default WhoAmIProvider;
36 |
--------------------------------------------------------------------------------
/src/query/post-query.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/src/query/post-query.ts
--------------------------------------------------------------------------------
/src/script/dummy-users.ts:
--------------------------------------------------------------------------------
1 | import { faker } from "@faker-js/faker";
2 |
3 | type SocialClass =
4 | | "nerd"
5 | | "hot"
6 | | "popular"
7 | | "ghost"
8 | | "gamer"
9 | | "artist"
10 | | "rebel"
11 | | "geek"
12 | | "athlete";
13 |
14 | interface User {
15 | name: string;
16 | age: number;
17 | interest: string;
18 | bio: string;
19 | nickname: string;
20 | socialRating: number;
21 | socialClass: SocialClass[];
22 | }
23 |
24 | const getRandomSocialClasses = (): SocialClass[] => {
25 | const classes: SocialClass[] = [
26 | "nerd",
27 | "hot",
28 | "popular",
29 | "ghost",
30 | "gamer",
31 | "artist",
32 | "rebel",
33 | "geek",
34 | "athlete",
35 | ];
36 | return faker.helpers.arrayElements(classes, { min: 1, max: 3 });
37 | };
38 |
39 | export const generateUsers = (count: number): User[] => {
40 | return Array.from({ length: count }, (_, index) => ({
41 | id: index + 1,
42 | name: faker.person.fullName(),
43 | age: faker.number.int({ min: 18, max: 50 }),
44 | interest: faker.word.noun(),
45 | bio: faker.lorem.sentence(),
46 | nickname: faker.internet.username(),
47 | socialRating: faker.number.float({ min: 0, max: 10 }),
48 | socialClass: getRandomSocialClasses(),
49 | }));
50 | };
51 |
52 | // const fetchedusers = generateUsers(100);
53 |
54 | // export default fetchedusers;
55 |
--------------------------------------------------------------------------------
/src/store/community.store.ts:
--------------------------------------------------------------------------------
1 | // import { CommunityInterface } from "@/interface/auth/community.interface";
2 | // import { create } from "zustand";
3 |
4 | // interface CommunityStore {
5 | // activeView: "my-communities" | "discover";
6 | // setActiveView: (view: "my-communities" | "discover") => void;
7 | // communityToEdit: CommunityInterface | null;
8 | // setCommunityToEdit: (community: | undefined) => void;
9 | // isFormModalOpen: boolean;
10 | // setFormModalOpen: (isOpen: boolean) => void;
11 | // }
12 |
13 | // const useCommunityStore = create((set) => ({
14 | // activeView: "my-communities",
15 | // setActiveView: (view) => set({ activeView: view }),
16 | // communityToEdit: {} as CommunityInterface,
17 | // setCommunityToEdit: (community) => set({ communityToEdit: community }),
18 | // isFormModalOpen: false,
19 | // setFormModalOpen: (isOpen) => set({ isFormModalOpen: isOpen }),
20 | // }));
21 |
22 | // export default useCommunityStore;
23 |
--------------------------------------------------------------------------------
/src/store/data.store.ts:
--------------------------------------------------------------------------------
1 | import {create} from "zustand";
2 |
3 | interface SearchState {
4 | search: string;
5 | setSearch: (search: string) => void;
6 | }
7 |
8 | const useSearchStore = create((set)=>({
9 | search: "",
10 | setSearch: (search) => set({search})
11 | }))
12 |
13 | export default useSearchStore;
--------------------------------------------------------------------------------
/src/store/fileStore.ts:
--------------------------------------------------------------------------------
1 | import { create } from "zustand";
2 |
3 | type FileWithPreview = {
4 | file: File;
5 | id: string;
6 | preview: string;
7 | type: "image" | "video" | "gif";
8 | };
9 |
10 | interface FileStore {
11 | files: FileWithPreview[];
12 | error: string | null;
13 | addFiles: (newFiles: FileWithPreview[]) => void;
14 | removeFile: (id: string) => void;
15 | clearFiles: () => void;
16 | setError: (error: string | null) => void;
17 | clearError: () => void;
18 | setFiles: (files: FileWithPreview[]) => void;
19 | }
20 |
21 | export const useFileStore = create((set) => ({
22 | files: [],
23 | error: null,
24 | addFiles: (newFiles) =>
25 | set((state) => ({
26 | files: [...state.files, ...newFiles].slice(0, 4),
27 | })),
28 | removeFile: (id) =>
29 | set((state) => ({
30 | files: state.files.filter((file) => file.id !== id),
31 | })),
32 | clearFiles: () => set({ files: [] }),
33 | setError: (error) => set({ error }),
34 | clearError: () => set({ error: null }),
35 | setFiles: (files) => set({ files }),
36 | }));
37 |
--------------------------------------------------------------------------------
/src/store/post.store.ts:
--------------------------------------------------------------------------------
1 | import postInterface from "@/interface/auth/post.interface";
2 | import { create } from "zustand";
3 |
4 | interface PostStoreInterface {
5 | posts: postInterface[];
6 | selectedPost: postInterface | null;
7 | setSelectedPost: (post: postInterface) => void;
8 | content: string;
9 | setContent: (content: string) => void;
10 | }
11 |
12 | const usePostStore = create((set) => ({
13 | posts: [],
14 | selectedPost: null,
15 | setSelectedPost: (post: postInterface) =>
16 | set({
17 | selectedPost: post,
18 | content: post.content,
19 | }),
20 | content: "",
21 | setContent: (content: string) =>
22 | set({
23 | content,
24 | }),
25 | }));
26 |
27 | export default usePostStore;
28 |
--------------------------------------------------------------------------------
/src/store/project-update.store.ts:
--------------------------------------------------------------------------------
1 | import { UpdateInterface } from "@/interface/auth/project.interface";
2 | import { create } from "zustand";
3 |
4 | interface ProjectUpdateInterface {
5 | projectUpdate: UpdateInterface;
6 | setProjectUpdate: (projectUpdate: UpdateInterface) => void;
7 | reset: () => void;
8 | }
9 |
10 | const useProjectUpdateStore = create((set) => ({
11 | projectUpdate: {} as UpdateInterface,
12 | setProjectUpdate: (projectUpdateIncoming: UpdateInterface) => {
13 | set({
14 | projectUpdate: projectUpdateIncoming,
15 | });
16 | },
17 | reset: () => {
18 | set({
19 | projectUpdate: {} as UpdateInterface,
20 | });
21 | },
22 | }));
23 |
24 | export default useProjectUpdateStore;
25 |
--------------------------------------------------------------------------------
/src/store/report.strore.ts:
--------------------------------------------------------------------------------
1 | import { create } from "zustand";
2 |
3 | interface reportStore {
4 | commentId?: string;
5 | postId?: string;
6 | reason: string;
7 | additionalContext: string;
8 | setCommentId: (commentId: string) => void;
9 | setPostId: (postId: string) => void;
10 | setReason: (reason: string) => void;
11 | setAdditionalContext: (context: string) => void;
12 | }
13 |
14 | const useReportStore = create((set) => ({
15 | commentId: "",
16 | postId: "",
17 | reason: "",
18 | additionalContext: "",
19 |
20 | setCommentId: (commentId: string) => {
21 | set({
22 | commentId: commentId,
23 | });
24 | },
25 |
26 | setPostId: (postId: string) => {
27 | set({
28 | postId: postId,
29 | });
30 | },
31 |
32 | setReason: (reason: string) => {
33 | set({
34 | reason: reason,
35 | });
36 | },
37 |
38 | setAdditionalContext: (context: string) => {
39 | set({
40 | additionalContext: context,
41 | });
42 | },
43 | }));
44 |
45 | export default useReportStore;
46 |
--------------------------------------------------------------------------------
/src/store/search.store.ts:
--------------------------------------------------------------------------------
1 | import { create } from "zustand";
2 |
3 | interface SearchState {
4 | query: string;
5 | setQuery: (query: string) => void;
6 | isSearchOpen: boolean;
7 | setIsSearchOpen: (isOpen: boolean) => void;
8 | }
9 |
10 | const useSearchStore = create((set) => ({
11 | query: "",
12 | setQuery: (query) => set({ query }),
13 | isSearchOpen: false,
14 | setIsSearchOpen: (isOpen) => set({ isSearchOpen: isOpen }),
15 | }));
16 |
17 | export default useSearchStore;
18 |
--------------------------------------------------------------------------------
/src/store/useFormStore.ts:
--------------------------------------------------------------------------------
1 | import { create } from "zustand";
2 | import { Country } from "../components/ui/country-dropdown";
3 |
4 | interface FormState {
5 | selectedCountry: Country | null;
6 | selectedCoverImage: File | null; // Add this line
7 | selectedImage: File | null;
8 | nerdAt: string;
9 | bio: string;
10 | displayName: string;
11 | link: string;
12 | setSelectedCountry: (country: Country) => void;
13 | setSelectedImage: (image: File) => void;
14 | setNerdAt: (value: string) => void;
15 | setBio: (value: string) => void;
16 | setDisplayName: (value: string) => void;
17 | setLink: (value: string) => void;
18 | setSelectedCoverImage: (image: File) => void; // Add this line
19 | }
20 |
21 | export const useFormStore = create((set) => ({
22 | selectedCountry: null,
23 | selectedImage: null,
24 | nerdAt: "",
25 | bio: "",
26 | displayName: "",
27 | link: "",
28 | setSelectedCountry: (country) => set({ selectedCountry: country }),
29 | setSelectedImage: (image) => set({ selectedImage: image }),
30 | setNerdAt: (value) => set({ nerdAt: value }),
31 | setBio: (value) => set({ bio: value }),
32 | setDisplayName: (value) => set({ displayName: value }),
33 | setLink: (value) => set({ link: value }),
34 | selectedCoverImage: null, // Add this line
35 | setSelectedCoverImage: (image) => set({ selectedCoverImage: image }), // Add this line
36 | }));
37 |
--------------------------------------------------------------------------------
/src/store/user.store.ts:
--------------------------------------------------------------------------------
1 | import UserInterface from "@/interface/auth/user.interface";
2 | import { create } from "zustand";
3 | import { useFormStore } from "./useFormStore";
4 |
5 | interface userStoreInterface {
6 | isloading: boolean;
7 | setIsLoading: (data: boolean) => void;
8 | user: UserInterface;
9 | setuser: (newUser: UserInterface) => void;
10 | }
11 |
12 | const useUserStore = create((set) => ({
13 | isloading: true,
14 | setIsLoading: (data) => {
15 | set({
16 | isloading: data,
17 | });
18 | },
19 | user: {} as UserInterface,
20 | setuser: (newUser: UserInterface) => {
21 | set(() => ({ user: newUser }));
22 | useFormStore.setState({
23 | nerdAt: newUser.nerdAt!,
24 | bio: newUser.bio!,
25 | displayName: newUser.visualName!,
26 | link: newUser.link!,
27 | // selectedImage: newUser.image!,
28 | // selectedCountry: newUser.country,
29 | });
30 | },
31 | }));
32 |
33 | export default useUserStore;
34 |
--------------------------------------------------------------------------------
/src/store/userProfile.store.ts:
--------------------------------------------------------------------------------
1 | import { create } from "zustand";
2 | import UserInterface from "@/interface/auth/user.interface";
3 |
4 | interface UserProfileState {
5 | userProfile: UserInterface | null;
6 | setUserProfile: (user: UserInterface) => void;
7 | }
8 |
9 | const useUserProfileStore = create((set) => ({
10 | userProfile: null,
11 | setUserProfile: (user) => set({ userProfile: user }),
12 | }));
13 |
14 | export default useUserProfileStore;
15 |
--------------------------------------------------------------------------------
/src/types/apiResponses.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeabnoah/Nerdspace_codebase1/011d459664f499b2a0fd7d898f93c070d62e34b7/src/types/apiResponses.ts
--------------------------------------------------------------------------------
/src/validation/bookmark.validation.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | const bookmarkSchema = z.object({
4 | userId: z.string(),
5 | postId: z.string(),
6 | });
7 |
8 | export default bookmarkSchema;
9 |
--------------------------------------------------------------------------------
/src/validation/bug.validation.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | export const bugReportSchema = z.object({
4 | content: z.string().min(1, "Content is required"),
5 | bugseverity: z.enum(["LOW", "MEDIUM", "HIGH"]).optional(),
6 | status: z.enum(["PENDING", "RESOLVED", "REJECTED"]).optional(),
7 | });
8 |
--------------------------------------------------------------------------------
/src/validation/comment.validation.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | const commentSchema = z.object({
4 | postId: z.string(),
5 | content: z.string(),
6 | parentId: z.string().optional(),
7 | });
8 |
9 | export default commentSchema;
10 |
--------------------------------------------------------------------------------
/src/validation/like.validation.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | const likeSchema = z.object({
4 | postId: z.string(),
5 | });
6 |
7 | export default likeSchema;
8 |
--------------------------------------------------------------------------------
/src/validation/login.validation.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | const errorMessages = {
4 | email: {
5 | required: "Email is required",
6 | },
7 | password: {
8 | required: "Password is required",
9 | regex:
10 | "Password must be at least 8 characters long, include at least one uppercase letter, one lowercase letter, one number, and one special character",
11 | },
12 | };
13 |
14 | export const loginSchema = z.object({
15 | email: z.string().nonempty({ message: errorMessages.email.required }),
16 |
17 | password: z.string().nonempty({ message: errorMessages.password.required }),
18 | });
19 |
20 | export type loginType = z.infer;
21 |
--------------------------------------------------------------------------------
/src/validation/post.validation.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | const postSchema = z.object({
4 | content: z
5 | .string()
6 | .trim()
7 | .min(4, { message: "Content must be at least 4 characters long" })
8 | .refine((val) => val.split(/\s+/).length <= 500, {
9 | message: "Content cannot exceed 500 words",
10 | }),
11 | fileUrls: z.array(z.string()).optional(),
12 | projectId: z.string().optional(),
13 | });
14 |
15 | export type PostType = z.infer;
16 |
17 | export { postSchema };
18 |
--------------------------------------------------------------------------------
/src/validation/project.validation.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | export const projectSchema = z.object({
4 | name: z.string(),
5 | image: z.string().url(),
6 | description: z.string(),
7 | category: z.array(z.string()),
8 | access: z.enum(["public", "private"]).optional(),
9 | status: z.enum(["ONGOING", "COMPLETED", "CANCELLED", "PAUSED"]).optional(),
10 | });
11 |
12 | export type ProjectInterface = z.infer;
13 |
--------------------------------------------------------------------------------
/src/validation/report.validation.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | export const reportSchema = z
4 | .object({
5 | postId: z.string().optional().nullable(),
6 | commentId: z.string().optional().nullable(),
7 | reason: z.string().min(1, "Reason is required"),
8 | additionalContext: z.string().optional().nullable(),
9 | })
10 | .refine((data) => data.postId || data.commentId, {
11 | message: "Either postId or commentId must be provided",
12 | });
13 |
14 | export type reportType = z.infer;
15 |
--------------------------------------------------------------------------------
/src/validation/reset-pass.validation.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | const strongPasswordRegex =
4 | /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
5 |
6 | const errorMessages = {
7 | password: {
8 | required: "Password is required",
9 | regex:
10 | "Password must be at least 8 characters long, include at least one uppercase letter, one lowercase letter, one number, and one special character",
11 | },
12 | confirmPassword: {
13 | required: "Confirm Password is required",
14 | match: "Passwords do not match",
15 | },
16 | };
17 |
18 | export const resetPasswordSchema = z
19 | .object({
20 | password: z
21 | .string()
22 | .nonempty({ message: errorMessages.password.required })
23 | .regex(strongPasswordRegex, { message: errorMessages.password.regex }),
24 | confirmPassword: z
25 | .string()
26 | .nonempty({ message: errorMessages.confirmPassword.required }),
27 | })
28 | .refine((data) => data.password === data.confirmPassword, {
29 | message: errorMessages.confirmPassword.match,
30 | path: ["confirmPassword"],
31 | });
32 |
33 | export type resetPasswordType = z.infer;
34 |
--------------------------------------------------------------------------------
/src/validation/signup.validation.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | const strongPasswordRegex =
4 | /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,}$/;
5 |
6 | const errorMessages = {
7 | name: {
8 | required: "Name is required",
9 | min: "Name must be at least 2 characters long",
10 | },
11 | email: {
12 | required: "Email is required",
13 | invalid: "Please enter a valid email address",
14 | },
15 | password: {
16 | required: "Password is required",
17 | regex:
18 | "Password must be at least 8 characters long, include at least one uppercase letter, one lowercase letter, one number, and one special character",
19 | },
20 | };
21 |
22 | export const signupSchema = z.object({
23 | name: z
24 | .string()
25 | .min(2, { message: errorMessages.name.min })
26 | .nonempty({ message: errorMessages.name.required }),
27 |
28 | email: z
29 | .string()
30 | .email({ message: errorMessages.email.invalid })
31 | .nonempty({ message: errorMessages.email.required }),
32 |
33 | password: z
34 | .string()
35 | .nonempty({ message: errorMessages.password.required })
36 | .regex(strongPasswordRegex, { message: errorMessages.password.regex }),
37 | });
38 |
39 | export type SignupFormData = z.infer;
40 |
--------------------------------------------------------------------------------
/src/validation/verify-email.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | const errorMessages = {
4 | email: {
5 | required: "Email is required",
6 | invalid: "Please enter a valid email address",
7 | },
8 | };
9 |
10 | export const ForgotPasswordSchema = z.object({
11 | email: z
12 | .string()
13 | .email({ message: errorMessages.email.invalid })
14 | .nonempty({ message: errorMessages.email.required }),
15 | });
16 |
17 | export type ForgotPasswordType = z.infer;
18 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./src/*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "redirects": [
3 | {
4 | "source": "/(.*)",
5 | "destination": "https://1.nerdspacer.com",
6 | "statusCode": 301
7 | }
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------