From 9bb380e7c08fb073e5f37a1f02172e0e10db3f41 Mon Sep 17 00:00:00 2001 From: morisummer Date: Thu, 26 Jun 2025 17:01:53 +0300 Subject: [PATCH 1/9] mvp 1 --- frontend/src/app.css | 21 + frontend/src/lib/api/v1.d.ts | 647 +++++++++--------- .../src/lib/components/ui/badge/badge.svelte | 50 ++ frontend/src/lib/components/ui/badge/index.ts | 2 + .../src/lib/components/ui/separator/index.ts | 1 + .../components/ui/separator/separator.svelte | 25 + frontend/src/routes/app/+layout.svelte | 2 +- .../explore/(components)/ProjectCard.svelte | 141 +++- frontend/src/routes/app/explore/+page.svelte | 142 +++- frontend/src/routes/app/profile/+page.svelte | 315 +++++++++ .../app/projects/(components)/dataLoaders.ts | 18 + .../routes/app/projects/[slug]/+page.svelte | 260 +++++++ .../src/routes/app/projects/[slug]/+page.ts | 20 + 13 files changed, 1294 insertions(+), 350 deletions(-) create mode 100644 frontend/src/lib/components/ui/badge/badge.svelte create mode 100644 frontend/src/lib/components/ui/badge/index.ts create mode 100644 frontend/src/lib/components/ui/separator/index.ts create mode 100644 frontend/src/lib/components/ui/separator/separator.svelte create mode 100644 frontend/src/routes/app/profile/+page.svelte create mode 100644 frontend/src/routes/app/projects/(components)/dataLoaders.ts create mode 100644 frontend/src/routes/app/projects/[slug]/+page.svelte create mode 100644 frontend/src/routes/app/projects/[slug]/+page.ts 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..0660400 100644 --- a/frontend/src/lib/api/v1.d.ts +++ b/frontend/src/lib/api/v1.d.ts @@ -4,328 +4,339 @@ */ export interface paths { - '/api/health-check': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Health Check */ - get: operations['health_check_api_health_check_get']; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/api/projects/': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Get Projects */ - get: operations['get_projects_api_projects__get']; - put?: never; - /** Create Project */ - post: operations['create_project_api_projects__post']; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/api/projects/{project_id}': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Get Project By Id */ - get: operations['get_project_by_id_api_projects__project_id__get']; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/api/auth/token': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** Login For Access Token */ - post: operations['login_for_access_token_api_auth_token_post']; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - '/api/auth/register': { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** Register */ - post: operations['register_api_auth_register_post']; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; + "/api/health-check": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Health Check */ + get: operations["health_check_api_health_check_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/projects/": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Projects */ + get: operations["get_projects_api_projects__get"]; + put?: never; + /** Create Project */ + post: operations["create_project_api_projects__post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/projects/{project_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Project By Id */ + get: operations["get_project_by_id_api_projects__project_id__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/auth/token": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Login For Access Token */ + post: operations["login_for_access_token_api_auth_token_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/auth/register": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Register */ + post: operations["register_api_auth_register_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; } export type webhooks = Record; export interface components { - schemas: { - /** Body_login_for_access_token_api_auth_token_post */ - Body_login_for_access_token_api_auth_token_post: { - /** Grant Type */ - grant_type?: string | null; - /** Username */ - username: string; - /** Password */ - password: string; - /** - * Scope - * @default - */ - scope: string; - /** Client Id */ - client_id?: string | null; - /** Client Secret */ - client_secret?: string | null; - }; - /** HTTPValidationError */ - HTTPValidationError: { - /** Detail */ - detail?: components['schemas']['ValidationError'][]; - }; - /** ProjectSchema */ - ProjectSchema: { - /** Title */ - title: string; - /** Description */ - description: string | null; - /** Is Public */ - is_public: boolean; - }; - /** Token */ - Token: { - /** Access Token */ - access_token: string; - /** Token Type */ - token_type: string; - }; - /** UserRegister */ - UserRegister: { - /** Username */ - username: string; - /** Password */ - password: string; - }; - /** ValidationError */ - ValidationError: { - /** Location */ - loc: (string | number)[]; - /** Message */ - msg: string; - /** Error Type */ - type: string; - }; - }; - responses: never; - parameters: never; - requestBodies: never; - headers: never; - pathItems: never; + schemas: { + /** Body_login_for_access_token_api_auth_token_post */ + Body_login_for_access_token_api_auth_token_post: { + /** Grant Type */ + grant_type?: string | null; + /** Username */ + username: string; + /** Password */ + password: string; + /** + * Scope + * @default + */ + scope: string; + /** Client Id */ + client_id?: string | null; + /** Client Secret */ + client_secret?: string | null; + }; + /** HTTPValidationError */ + HTTPValidationError: { + /** Detail */ + detail?: components["schemas"]["ValidationError"][]; + }; + /** NewProjectSchema */ + NewProjectSchema: { + /** Title */ + title: string; + /** Description */ + description: string | null; + /** Is Public */ + is_public: boolean; + }; + /** ProjectSchema */ + ProjectSchema: { + /** Title */ + title: string; + /** Description */ + description: string | null; + /** Is Public */ + is_public: boolean; + /** Id */ + id: number; + }; + /** Token */ + Token: { + /** Access Token */ + access_token: string; + /** Token Type */ + token_type: string; + }; + /** UserRegister */ + UserRegister: { + /** Username */ + username: string; + /** Password */ + password: string; + }; + /** ValidationError */ + ValidationError: { + /** Location */ + loc: (string | number)[]; + /** Message */ + msg: string; + /** Error Type */ + type: string; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; } export type $defs = Record; export interface operations { - health_check_api_health_check_get: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': unknown; - }; - }; - }; - }; - get_projects_api_projects__get: { - 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']['ProjectSchema'][]; - }; - }; - }; - }; - create_project_api_projects__post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - 'application/json': components['schemas']['ProjectSchema']; - }; - }; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['ProjectSchema']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - get_project_by_id_api_projects__project_id__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']['ProjectSchema']; - }; - }; - /** @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; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - 'application/x-www-form-urlencoded': components['schemas']['Body_login_for_access_token_api_auth_token_post']; - }; - }; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['Token']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; - register_api_auth_register_post: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - 'application/json': components['schemas']['UserRegister']; - }; - }; - responses: { - /** @description Successful Response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['Token']; - }; - }; - /** @description Validation Error */ - 422: { - headers: { - [name: string]: unknown; - }; - content: { - 'application/json': components['schemas']['HTTPValidationError']; - }; - }; - }; - }; + health_check_api_health_check_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + get_projects_api_projects__get: { + 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"]["ProjectSchema"][]; + }; + }; + }; + }; + create_project_api_projects__post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["NewProjectSchema"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectSchema"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_project_by_id_api_projects__project_id__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"]["ProjectSchema"]; + }; + }; + /** @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; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/x-www-form-urlencoded": components["schemas"]["Body_login_for_access_token_api_auth_token_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Token"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + register_api_auth_register_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UserRegister"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Token"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; } 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..5000457 --- /dev/null +++ b/frontend/src/lib/components/ui/badge/badge.svelte @@ -0,0 +1,50 @@ + + + + + + {@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..64e0aa9 --- /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/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..64e1133 --- /dev/null +++ b/frontend/src/lib/components/ui/separator/separator.svelte @@ -0,0 +1,25 @@ + + +
diff --git a/frontend/src/routes/app/+layout.svelte b/frontend/src/routes/app/+layout.svelte index aefdc6b..cc3e45d 100644 --- a/frontend/src/routes/app/+layout.svelte +++ b/frontend/src/routes/app/+layout.svelte @@ -65,7 +65,7 @@ {userState.user?.username} - + goto('/app/profile')}> Profile diff --git a/frontend/src/routes/app/explore/(components)/ProjectCard.svelte b/frontend/src/routes/app/explore/(components)/ProjectCard.svelte index 593d7c1..8c28af3 100644 --- a/frontend/src/routes/app/explore/(components)/ProjectCard.svelte +++ b/frontend/src/routes/app/explore/(components)/ProjectCard.svelte @@ -1,24 +1,133 @@ - - - - {project.title} - - +{#if viewMode === 'grid'} + + +
+ + {project.title} + + + {#if project.is_public} + + Public + {:else} + + Private + {/if} + +
+
+ + +

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

+ +
+
+ + Project Owner +
+
+ + {formatDate()} +
+
+
- -

{project.description}

-
- - - -
\ No newline at end of file + + + +
+{: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 e5caa70..7211dde 100644 --- a/frontend/src/routes/app/explore/+page.svelte +++ b/frontend/src/routes/app/explore/+page.svelte @@ -2,29 +2,141 @@ 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.

+
+ +
+
+
+

Explore Projects

+

Discover amazing projects from the community

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

+ 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} - {/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..94b53ba --- /dev/null +++ b/frontend/src/routes/app/profile/+page.svelte @@ -0,0 +1,315 @@ + + +
+ +
+
+

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} +