diff --git a/frontend/src/app.css b/frontend/src/app.css index b62d430..43cee97 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -119,3 +119,24 @@ @apply bg-background text-foreground; } } + +@layer utilities { + .line-clamp-1 { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + } + .line-clamp-2 { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + } + .line-clamp-3 { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; + } +} diff --git a/frontend/src/lib/api/v1.d.ts b/frontend/src/lib/api/v1.d.ts index a93a51a..cad8302 100644 --- a/frontend/src/lib/api/v1.d.ts +++ b/frontend/src/lib/api/v1.d.ts @@ -56,6 +56,74 @@ export interface paths { patch?: never; trace?: never; }; + '/api/projects/{project_id}/apply': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Apply To Project */ + post: operations['apply_to_project_api_projects__project_id__apply_post']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/projects/{project_id}/applications': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Project Applications */ + get: operations['get_project_applications_api_projects__project_id__applications_get']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/projects/{project_id}/new-applications': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Project New Applications */ + get: operations['get_project_new_applications_api_projects__project_id__new_applications_get']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/projects/{project_id}/applications/approve': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** Approve Application */ + patch: operations['approve_application_api_projects__project_id__applications_approve_patch']; + trace?: never; + }; '/api/auth/token': { parameters: { query?: never; @@ -90,10 +158,65 @@ export interface paths { patch?: never; trace?: never; }; + '/api/user/set-role': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Set User Role */ + post: operations['set_user_role_api_user_set_role_post']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/user/get-me': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Get Me */ + post: operations['get_me_api_user_get_me_post']; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; } export type webhooks = Record; export interface components { schemas: { + /** ApplicationSchema */ + ApplicationSchema: { + /** Project Id */ + project_id: number; + /** User Id */ + user_id: number; + /** Is Approved */ + is_approved: boolean | null; + /** + * Created At + * Format: date-time + */ + created_at: string; + }; + /** ApproveApplicationSchema */ + ApproveApplicationSchema: { + /** Is Approved */ + is_approved: boolean; + /** User Id */ + user_id: number; + }; /** Body_login_for_access_token_api_auth_token_post */ Body_login_for_access_token_api_auth_token_post: { /** Grant Type */ @@ -117,6 +240,26 @@ export interface components { /** Detail */ detail?: components['schemas']['ValidationError'][]; }; + /** NewProjectSchema */ + NewProjectSchema: { + /** Title */ + title: string; + /** Description */ + description: string | null; + /** Is Public */ + is_public: boolean | null; + /** Is Opensource */ + is_opensource: boolean | null; + /** Is Dead */ + is_dead: boolean | null; + }; + /** ProjectMemberSchema */ + ProjectMemberSchema: { + /** Project Id */ + project_id: number; + /** User Id */ + user_id: number; + }; /** ProjectSchema */ ProjectSchema: { /** Title */ @@ -124,7 +267,24 @@ export interface components { /** Description */ description: string | null; /** Is Public */ - is_public: boolean; + is_public: boolean | null; + /** Is Opensource */ + is_opensource: boolean | null; + /** Is Dead */ + is_dead: boolean | null; + /** Id */ + id: number; + /** + * Created At + * Format: date-time + */ + created_at: string; + /** Ceo Id */ + ceo_id: number | null; + }; + /** SetUserRoleSchema */ + SetUserRoleSchema: { + role: components['schemas']['UserRole']; }; /** Token */ Token: { @@ -133,6 +293,12 @@ export interface components { /** Token Type */ token_type: string; }; + /** User */ + User: { + /** Username */ + username: string; + role: components['schemas']['UserRole']; + }; /** UserRegister */ UserRegister: { /** Username */ @@ -140,6 +306,11 @@ export interface components { /** Password */ password: string; }; + /** + * UserRole + * @enum {string} + */ + UserRole: 'VIEWER' | 'FOUNDER' | 'DEVELOPER' | 'INVESTOR'; /** ValidationError */ ValidationError: { /** Location */ @@ -207,7 +378,7 @@ export interface operations { }; requestBody: { content: { - 'application/json': components['schemas']['ProjectSchema']; + 'application/json': components['schemas']['NewProjectSchema']; }; }; responses: { @@ -262,6 +433,134 @@ export interface operations { }; }; }; + apply_to_project_api_projects__project_id__apply_post: { + parameters: { + query?: never; + header?: never; + path: { + project_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['ApplicationSchema']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + get_project_applications_api_projects__project_id__applications_get: { + parameters: { + query?: never; + header?: never; + path: { + project_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['ApplicationSchema'][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + get_project_new_applications_api_projects__project_id__new_applications_get: { + parameters: { + query?: never; + header?: never; + path: { + project_id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['ApplicationSchema'][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + approve_application_api_projects__project_id__applications_approve_patch: { + parameters: { + query?: never; + header?: never; + path: { + project_id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['ApproveApplicationSchema']; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['ProjectMemberSchema']; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; login_for_access_token_api_auth_token_post: { parameters: { query?: never; @@ -328,4 +627,59 @@ export interface operations { }; }; }; + set_user_role_api_user_set_role_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + 'application/json': components['schemas']['SetUserRoleSchema']; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': { + [key: string]: string; + }; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['HTTPValidationError']; + }; + }; + }; + }; + get_me_api_user_get_me_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['User']; + }; + }; + }; + }; } diff --git a/frontend/src/lib/assets/placeholder.svg b/frontend/src/lib/assets/placeholder.svg new file mode 100644 index 0000000..e763910 --- /dev/null +++ b/frontend/src/lib/assets/placeholder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/lib/components/ui/badge/badge.svelte b/frontend/src/lib/components/ui/badge/badge.svelte new file mode 100644 index 0000000..7c242f7 --- /dev/null +++ b/frontend/src/lib/components/ui/badge/badge.svelte @@ -0,0 +1,49 @@ + + + + + + {@render children?.()} + diff --git a/frontend/src/lib/components/ui/badge/index.ts b/frontend/src/lib/components/ui/badge/index.ts new file mode 100644 index 0000000..f05fb87 --- /dev/null +++ b/frontend/src/lib/components/ui/badge/index.ts @@ -0,0 +1,2 @@ +export { default as Badge } from './badge.svelte'; +export { badgeVariants, type BadgeVariant } from './badge.svelte'; diff --git a/frontend/src/lib/components/ui/checkbox/checkbox.svelte b/frontend/src/lib/components/ui/checkbox/checkbox.svelte new file mode 100644 index 0000000..fc8614a --- /dev/null +++ b/frontend/src/lib/components/ui/checkbox/checkbox.svelte @@ -0,0 +1,36 @@ + + + + {#snippet children({ checked, indeterminate })} +
+ {#if checked} + + {:else if indeterminate} + + {/if} +
+ {/snippet} +
diff --git a/frontend/src/lib/components/ui/checkbox/index.ts b/frontend/src/lib/components/ui/checkbox/index.ts new file mode 100644 index 0000000..5c27671 --- /dev/null +++ b/frontend/src/lib/components/ui/checkbox/index.ts @@ -0,0 +1,6 @@ +import Root from './checkbox.svelte'; +export { + Root, + // + Root as Checkbox +}; diff --git a/frontend/src/lib/components/ui/separator/index.ts b/frontend/src/lib/components/ui/separator/index.ts new file mode 100644 index 0000000..f3863a6 --- /dev/null +++ b/frontend/src/lib/components/ui/separator/index.ts @@ -0,0 +1 @@ +export { default as Separator } from './separator.svelte'; diff --git a/frontend/src/lib/components/ui/separator/separator.svelte b/frontend/src/lib/components/ui/separator/separator.svelte new file mode 100644 index 0000000..e978b08 --- /dev/null +++ b/frontend/src/lib/components/ui/separator/separator.svelte @@ -0,0 +1,25 @@ + + +
diff --git a/frontend/src/lib/components/ui/switch/index.ts b/frontend/src/lib/components/ui/switch/index.ts new file mode 100644 index 0000000..129f8f5 --- /dev/null +++ b/frontend/src/lib/components/ui/switch/index.ts @@ -0,0 +1,7 @@ +import Root from './switch.svelte'; + +export { + Root, + // + Root as Switch +}; diff --git a/frontend/src/lib/components/ui/switch/switch.svelte b/frontend/src/lib/components/ui/switch/switch.svelte new file mode 100644 index 0000000..ce291f3 --- /dev/null +++ b/frontend/src/lib/components/ui/switch/switch.svelte @@ -0,0 +1,29 @@ + + + + + diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index 89f3a28..3cc88dd 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -2,10 +2,12 @@ import '../app.css'; import { QueryClientProvider } from '@tanstack/svelte-query'; import { Toaster } from '$lib/components/ui/sonner/index.js'; + import { ModeWatcher } from 'mode-watcher'; let { data, children } = $props(); + {@render children()} diff --git a/frontend/src/routes/+layout.ts b/frontend/src/routes/+layout.ts index 593cfc8..201d9c9 100644 --- a/frontend/src/routes/+layout.ts +++ b/frontend/src/routes/+layout.ts @@ -1,4 +1,5 @@ export const prerender = true; +export const ssr = false; import { browser } from '$app/environment'; import { QueryClient } from '@tanstack/svelte-query'; diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index 93797b4..5c7fa5b 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -1,77 +1,399 @@ -

Welcome to ProjectOR

-
- - - -
+
+
+ + + ProjectOR + + +
+ + +
+
-

Implemented components

-
- - - - - - - -
-
- - - Card Title - - -

This is a simple card component.

-
-
- - - Card with Skeleton - - - - - - -
-
- - - - - - - -
-
- - - U - - - - U - - - - U - +
+
+
+
+
+
+ 🚀 Where Ideas Meet Execution +

+ Connect. Build. Launch. +

+

+ The ultimate platform connecting creators, developers, and investors. Turn your + startup ideas into reality with the right team and funding. +

+
+
+ + +
+
+
+ + Free to start +
+
+ + No credit card required +
+
+
+
+
+ Platform Dashboard +
+
+
+
+
+
+ +
+
+
+
+ Platform Features +

+ Everything you need to launch +

+

+ From idea conception to successful launch, our platform provides all the tools and + connections you need. +

+
+
+
+ + + + Idea Hub + + Post your startup ideas and get feedback from the community. Browse innovative + concepts looking for co-founders. + + + +
    +
  • + + Idea validation tools +
  • +
  • + + Community feedback +
  • +
  • + + Market research insights +
  • +
+
+
+ + + + + Developer Hub + + Discover exciting projects to join. Connect with founders and build the next big + thing together. + + + +
    +
  • + + Project matching +
  • +
  • + + Skill-based filtering +
  • +
  • + + Collaboration tools +
  • +
+
+
+ + + + + Team Collaboration + + Built-in tools for seamless team communication, project management, and progress + tracking. + + + +
    +
  • + + Real-time messaging +
  • +
  • + + Task management +
  • +
  • + + Progress analytics +
  • +
+
+
+
+
+
+ +
+
+
+
+ For Everyone +

+ Built for every role in the startup ecosystem +

+
+
+
+
+ + + + Viewers + + +

+ Explore ideas, follow projects, and stay updated on the latest innovations. +

+
+
+ + + + + Developers + + +

+ Find exciting projects, showcase your skills, and join innovative teams. +

+
+
+ + + + + Founders + + +

+ Share your vision, build your team, and turn ideas into successful startups. +

+
+
+ + + + + Investors + + +

+ Discover promising startups, connect with founders, and make strategic + investments. +

+
+
+
+
+ Team Collaboration +
+
+
+
+ +
+
+
+
+
+ + + Learn from Failure + +

+ The Startup Graveyard +

+

+ Learn from failed ventures. Our unique graveyard section documents startup failures + with detailed analysis, helping you avoid common pitfalls and make better decisions. +

+
+
+
+ +
+

Failure Analysis

+

+ Detailed breakdowns of what went wrong +

+
+
+
+ +
+

Key Learnings

+

+ Actionable insights for future projects +

+
+
+
+ +
+
+ Startup Graveyard Analytics +
+
+
+
+ +
+
+
+
+
10K+
+
Active Users
+
+
+
500+
+
Projects Launched
+
+
+
$2M+
+
Funding Raised
+
+
+
95%
+
Success Rate
+
+
+
+
+ +
+
+
+
+

+ Ready to turn your idea into reality? +

+

+ Join thousands of creators, developers, and investors building the future together. +

+
+
+ +
+
+
+
+
+ +
diff --git a/frontend/src/routes/app/(components)/ChooseRoleDialog.svelte b/frontend/src/routes/app/(components)/ChooseRoleDialog.svelte new file mode 100644 index 0000000..a152966 --- /dev/null +++ b/frontend/src/routes/app/(components)/ChooseRoleDialog.svelte @@ -0,0 +1,125 @@ + + + + + + Choose Your Role + + Select the role that best describes you to personalize your experience + + + +
+ {#each roles as role (role.value)} + {@const isSelected = selectedRole === role.value} + {@const Icon = role.icon} + + {/each} +
+ +
+ +
+
+
diff --git a/frontend/src/routes/app/(components)/CreateProjectDialog.svelte b/frontend/src/routes/app/(components)/CreateProjectDialog.svelte index b3b63fe..2b79615 100644 --- a/frontend/src/routes/app/(components)/CreateProjectDialog.svelte +++ b/frontend/src/routes/app/(components)/CreateProjectDialog.svelte @@ -4,6 +4,7 @@ import { Input } from '$lib/components/ui/input/index.js'; import { Label } from '$lib/components/ui/label/index.js'; import { Textarea } from '@/components/ui/textarea'; + import { Switch } from '$lib/components/ui/switch/index.js'; import { createMutation, useQueryClient } from '@tanstack/svelte-query'; import { toast } from 'svelte-sonner'; import { createProject } from './dataLoaders'; @@ -11,6 +12,8 @@ let name = $state(''); let description = $state(''); + let isOpensource = $state(false); + let isDead = $state(false); let { open = $bindable(false) }: { open: boolean } = $props(); const id = $props.id(); @@ -18,14 +21,25 @@ const queryClient = useQueryClient(); const createProjectMutation = createMutation({ - mutationFn: async ({ title, description }: { title: string; description: string }) => - await createProject(title, description), + mutationFn: async ({ + title, + description, + isOpensource, + isDead + }: { + title: string; + description: string; + isOpensource: boolean; + isDead: boolean; + }) => await createProject(title, description, isOpensource, isDead), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['projects'] }); toast.success('Project created successfully'); open = false; name = ''; description = ''; + isOpensource = false; + isDead = false; }, onError: (error) => { toast.error(`Failed to create project: ${error.message}`); @@ -53,13 +67,23 @@ placeholder="Enter a brief description of the project" />
+
+ + +
+
+ +
+ +

+ Mark if this project is no longer active. It will be listed in the startup graveyard + section. +

+
+
- - - + + + + +{:else} + + + +
+
+
+

+ {project.title} +

+ + {#if project.is_public} + + Public + {:else} + + Private + {/if} + +
+

+ {project.description || 'No description available.'} +

+
+
+ + Project Owner +
+
+ + {formatDate()} +
+
+
+
+ +
+
+
+
+{/if} diff --git a/frontend/src/routes/app/explore/+page.svelte b/frontend/src/routes/app/explore/+page.svelte index 56dabdb..a5e1cf5 100644 --- a/frontend/src/routes/app/explore/+page.svelte +++ b/frontend/src/routes/app/explore/+page.svelte @@ -2,27 +2,153 @@ import ProjectSkeleton from '$lib/components/skeletons/ProjectSkeleton.svelte'; import { getProjects } from './(components)/dataLoaders'; import { createQuery } from '@tanstack/svelte-query'; - import ErrorAlert from '@/components/ErrorAlert.svelte'; + import ErrorAlert from '$lib/components/ErrorAlert.svelte'; import ProjectCard from './(components)/ProjectCard.svelte'; + import { Button } from '$lib/components/ui/button'; + import { Input } from '$lib/components/ui/input'; + import * as Card from '$lib/components/ui/card'; + import { Search, Filter, Grid3X3, List } from '@lucide/svelte'; + import { type components } from '@/api/v1'; + + type Project = components['schemas']['ProjectSchema']; + + let searchTerm = $state(''); + let showPublicOnly = $state(false); + let viewMode = $state<'grid' | 'list'>('grid'); const projectsQuery = createQuery({ queryKey: ['projects'], queryFn: async () => await getProjects() }); + + $effect(() => { + // You can add search logic here if needed + }); + + const filteredProjects = $derived(() => { + if (!$projectsQuery.data) return []; + let filtered = $projectsQuery.data; + + if (searchTerm) { + filtered = filtered.filter( + (project: Project) => + project.title.toLowerCase().includes(searchTerm.toLowerCase()) || + (project.description && + project.description.toLowerCase().includes(searchTerm.toLowerCase())) + ); + } + + if (showPublicOnly) { + filtered = filtered.filter((project: Project) => project.is_public); + } + + return filtered; + }); -
- {#if $projectsQuery.isPending} - {#each Array(12) as _ (_)} - - {/each} - {:else if $projectsQuery.isError} - - {:else if $projectsQuery.data.length === 0} -

No projects found.

- {:else} - {#each $projectsQuery.data as project (project.title)} - - {/each} - {/if} +
+ +
+
+
+

Explore Projects

+

Discover amazing projects from the community

+
+
+ + +
+
+ + + +
+
+ + +
+
+ + +
+
+
+
+ + +
+ {#if $projectsQuery.isPending} +
+ {#each Array(12) as _ (_)} + + {/each} +
+ {:else if $projectsQuery.isError} +
+ +
+ {:else} + +
+

+ Showing {filteredProjects().length} of {$projectsQuery.data.length} projects +

+
+ + {#if filteredProjects().length === 0} +
+
+ +
+

No projects found

+

Try adjusting your search or filters

+ +
+ {:else} +
+ {#each filteredProjects() as project (project.title)} + + {/each} +
+ {/if} + {/if} +
diff --git a/frontend/src/routes/app/profile/+page.svelte b/frontend/src/routes/app/profile/+page.svelte new file mode 100644 index 0000000..9474015 --- /dev/null +++ b/frontend/src/routes/app/profile/+page.svelte @@ -0,0 +1,313 @@ + + +
+ +
+
+

Profile

+

Manage your account settings and preferences

+
+ {#if !isEditing} + + {/if} +
+ +
+ +
+ + +
+ + + + {userState.user?.username?.[0]?.toUpperCase() || 'U'} + + + {#if isEditing} + + {/if} +
+ +

+ {userState.user?.username || 'Username'} +

+

+ {editedProfile.email || 'email@example.com'} +

+ + + + Active User + + +
+ + Joined {formatDate(new Date())} +
+
+
+ + + + + Quick Stats + + +
+ Projects Created + 12 +
+
+ Public Projects + 8 +
+
+ Private Projects + 4 +
+
+
+
+ + +
+ + + +
+ Personal Information + Update your personal details +
+ {#if isEditing} +
+ + +
+ {/if} +
+ +
+
+ + {#if isEditing} + + {:else} +
+ + {userState.user?.username || 'Not set'} +
+ {/if} +
+ +
+ + {#if isEditing} + + {:else} +
+ + {editedProfile.email || 'Not set'} +
+ {/if} +
+
+ +
+
+ + {#if isEditing} + + {:else} +
+ {editedProfile.location || 'Not set'} +
+ {/if} +
+ +
+ + {#if isEditing} + + {:else} +
+ {editedProfile.website || 'Not set'} +
+ {/if} +
+
+ +
+ + {#if isEditing} +