diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index bbcbbe7..84a13eb 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -7,14 +7,16 @@ assignees: '' --- -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] +**Team:** + -**Describe the solution you'd like** -A clear and concise description of what you want to happen. +**Overview:** + + +**Additional Instructions:** + + +**Deadline:** + -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/prevent-main-pr.yml b/.github/workflows/prevent-main-pr.yml new file mode 100644 index 0000000..03f0f52 --- /dev/null +++ b/.github/workflows/prevent-main-pr.yml @@ -0,0 +1,15 @@ +name: Prevent PR to Main + +on: + pull_request: + branches: + - main # This triggers the action when a PR is made to "main" + +jobs: + block-main-pr: + runs-on: ubuntu-latest + steps: + - name: Fail PR if targeting main + run: | + echo "❌ Pull requests to 'main' are not allowed. Please target 'develop' instead." + exit 1 diff --git a/.idea/Dev-Track-App.iml b/.idea/Dev-Track-App.iml index 7e902b9..f72ad73 100644 --- a/.idea/Dev-Track-App.iml +++ b/.idea/Dev-Track-App.iml @@ -30,6 +30,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml index 935f4ca..d6245bd 100644 --- a/.idea/libraries/Dart_Packages.xml +++ b/.idea/libraries/Dart_Packages.xml @@ -37,10 +37,17 @@ + + + + + + - @@ -51,6 +58,20 @@ + + + + + + + + + + + + @@ -61,7 +82,49 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -75,7 +138,14 @@ - + + + + + + @@ -103,7 +173,7 @@ - @@ -114,6 +184,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -138,7 +264,7 @@ - @@ -163,6 +289,20 @@ + + + + + + + + + + + + @@ -173,21 +313,21 @@ - - - @@ -212,10 +352,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - @@ -226,6 +408,13 @@ + + + + + + @@ -233,6 +422,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -292,35 +530,35 @@ - - - - - @@ -341,7 +579,7 @@ - @@ -362,14 +600,21 @@ - + + + + + + - @@ -381,31 +626,65 @@ - + + + + - - + + + + + + + + + - + + + + + + + + + - + + + - - - + + + - + + + + + + + + + + + + + + + @@ -413,18 +692,19 @@ - - - - - + + + + + - + - - + + + diff --git a/.vscode/launch.json b/.vscode/launch.json index d30496b..95464f9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -16,7 +16,8 @@ ], "django": true, "autoStartBrowser": false, - "program": "${workspaceFolder}\\manage.py" + "justMyCode": false, + "program": "${workspaceFolder}/backend/myproject/manage.py" } ] } \ No newline at end of file diff --git a/backend/myproject/.env b/backend/myproject/.env new file mode 100644 index 0000000..361fe0b --- /dev/null +++ b/backend/myproject/.env @@ -0,0 +1,7 @@ +DB_NAME=postgres +DB_USER=postgres.swzgdimtkynmkzftxjjv +DB_PASSWORD=GOMxZBnMwtF5VPYk +DB_HOST=aws-0-ap-south-1.pooler.supabase.com +DB_PORT=6543 # Try 6543 if 5432 fails +DATABASE_URL=postgres://postgres:GOMxZBnMwtF5VPYk@aws-0-ap-south-1.pooler.supabase.com:6543/postgres?sslmode=require +DJANGO_ENV=test diff --git a/backend/myproject/announcements/__pycache__/__init__.cpython-313.pyc b/backend/myproject/announcements/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..9a677e7 Binary files /dev/null and b/backend/myproject/announcements/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend/myproject/announcements/__pycache__/admin.cpython-313.pyc b/backend/myproject/announcements/__pycache__/admin.cpython-313.pyc new file mode 100644 index 0000000..1b690e4 Binary files /dev/null and b/backend/myproject/announcements/__pycache__/admin.cpython-313.pyc differ diff --git a/backend/myproject/announcements/__pycache__/api.cpython-313.pyc b/backend/myproject/announcements/__pycache__/api.cpython-313.pyc new file mode 100644 index 0000000..421ff8e Binary files /dev/null and b/backend/myproject/announcements/__pycache__/api.cpython-313.pyc differ diff --git a/backend/myproject/announcements/__pycache__/apps.cpython-313.pyc b/backend/myproject/announcements/__pycache__/apps.cpython-313.pyc new file mode 100644 index 0000000..65bb02b Binary files /dev/null and b/backend/myproject/announcements/__pycache__/apps.cpython-313.pyc differ diff --git a/backend/myproject/announcements/__pycache__/models.cpython-313.pyc b/backend/myproject/announcements/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000..a47c39e Binary files /dev/null and b/backend/myproject/announcements/__pycache__/models.cpython-313.pyc differ diff --git a/backend/myproject/announcements/__pycache__/schemas.cpython-313.pyc b/backend/myproject/announcements/__pycache__/schemas.cpython-313.pyc new file mode 100644 index 0000000..58eb928 Binary files /dev/null and b/backend/myproject/announcements/__pycache__/schemas.cpython-313.pyc differ diff --git a/backend/myproject/announcements/__pycache__/services.cpython-313.pyc b/backend/myproject/announcements/__pycache__/services.cpython-313.pyc new file mode 100644 index 0000000..112e0ed Binary files /dev/null and b/backend/myproject/announcements/__pycache__/services.cpython-313.pyc differ diff --git a/backend/myproject/announcements/api.py b/backend/myproject/announcements/api.py new file mode 100644 index 0000000..2261c8b --- /dev/null +++ b/backend/myproject/announcements/api.py @@ -0,0 +1,52 @@ +from django.shortcuts import get_object_or_404 +from django.middleware.csrf import get_token +from ninja_extra import NinjaExtraAPI, ControllerBase, api_controller, route +from .models import PostModel +from . import schemas +from .schemas import PostSchema +from .services import AnnouncementService + +@api_controller("/posts", tags="Posts") +class PostAPIController(ControllerBase): + def __init__(self) -> None: + self.post_service = AnnouncementService() + + # API endpoint to create a new post (admin only). + @route.post("/add", url_name="Add Post") + def create_post(self, request, payload: schemas.PostCreateSchema): + admin_check = self.post_service._check_admin(request) + if admin_check: + return admin_check + + post = self.post_service.create_post(request, payload) + return { + "message": "Post created successfully", + "post_id": post.id, + "csrf_token": get_token(request) + } + + # API endpoint to list all posts. + @route.get("/", url_name="List Posts") + def list_posts(self, request): + return self.post_service.list_posts() + + @route.put("/{post_id}", url_name="Update Post", response=PostSchema) + def update_post(self, request, post_id: int, payload: schemas.PostUpdateSchema): + self.post_service._check_admin(request) + post = get_object_or_404(PostModel, id=post_id) + post = self.post_service.update_post(post, payload) + return schemas.PostSchema.from_orm(post) + + # API endpoint to delete a post (admin only). + @route.delete("/{post_id}", url_name="Delete Post") + def delete_post(self, request, post_id: int): + admin_check = self.post_service._check_admin(request) + if admin_check: + return admin_check + + post = get_object_or_404(PostModel, id=post_id) + self.post_service.delete_post(post) + return {"message": "Post deleted successfully"} + +# Register the PostAPIController with your NinjaExtraAPI instance. +api = NinjaExtraAPI(csrf=False) diff --git a/backend/myproject/announcements/migrations/0001_initial.py b/backend/myproject/announcements/migrations/0001_initial.py index a5c13e1..e7941e9 100644 --- a/backend/myproject/announcements/migrations/0001_initial.py +++ b/backend/myproject/announcements/migrations/0001_initial.py @@ -1,7 +1,5 @@ -# Generated by Django 5.0.7 on 2024-09-03 16:35 +# Generated by Django 5.1.6 on 2025-02-18 19:17 -import django.db.models.deletion -from django.conf import settings from django.db import migrations, models @@ -10,7 +8,6 @@ class Migration(migrations.Migration): initial = True dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ @@ -22,7 +19,6 @@ class Migration(migrations.Migration): ('created_at', models.DateTimeField(auto_now_add=True)), ('is_read', models.BooleanField(default=False)), ('notification_type', models.CharField(choices=[('EN', 'enrollment'), ('SC', 'scrum'), ('DE', 'deadline')], max_length=2)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), ] diff --git a/backend/myproject/announcements/migrations/0002_initial.py b/backend/myproject/announcements/migrations/0002_initial.py new file mode 100644 index 0000000..249959d --- /dev/null +++ b/backend/myproject/announcements/migrations/0002_initial.py @@ -0,0 +1,23 @@ +# Generated by Django 5.1.6 on 2025-02-18 19:17 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('announcements', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='notificationmodel', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/backend/myproject/announcements/migrations/0003_postmodel.py b/backend/myproject/announcements/migrations/0003_postmodel.py new file mode 100644 index 0000000..93abcf5 --- /dev/null +++ b/backend/myproject/announcements/migrations/0003_postmodel.py @@ -0,0 +1,26 @@ +# Generated by Django 5.1.6 on 2025-02-20 04:53 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('announcements', '0002_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='PostModel', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=100)), + ('description', models.TextField(blank=True, max_length=512)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/backend/myproject/announcements/migrations/__pycache__/0001_initial.cpython-313.pyc b/backend/myproject/announcements/migrations/__pycache__/0001_initial.cpython-313.pyc new file mode 100644 index 0000000..d238702 Binary files /dev/null and b/backend/myproject/announcements/migrations/__pycache__/0001_initial.cpython-313.pyc differ diff --git a/backend/myproject/announcements/migrations/__pycache__/0002_initial.cpython-313.pyc b/backend/myproject/announcements/migrations/__pycache__/0002_initial.cpython-313.pyc new file mode 100644 index 0000000..60fa7fa Binary files /dev/null and b/backend/myproject/announcements/migrations/__pycache__/0002_initial.cpython-313.pyc differ diff --git a/backend/myproject/announcements/migrations/__pycache__/0003_postmodel.cpython-313.pyc b/backend/myproject/announcements/migrations/__pycache__/0003_postmodel.cpython-313.pyc new file mode 100644 index 0000000..a2df112 Binary files /dev/null and b/backend/myproject/announcements/migrations/__pycache__/0003_postmodel.cpython-313.pyc differ diff --git a/backend/myproject/announcements/migrations/__pycache__/__init__.cpython-313.pyc b/backend/myproject/announcements/migrations/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..3abf1ca Binary files /dev/null and b/backend/myproject/announcements/migrations/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend/myproject/announcements/models.py b/backend/myproject/announcements/models.py index b896e38..40540c5 100644 --- a/backend/myproject/announcements/models.py +++ b/backend/myproject/announcements/models.py @@ -5,7 +5,7 @@ -class AnnouncementModel: +class PostModel(models.Model): title = models.CharField(max_length=100, blank=False, null=False) description = models.TextField(max_length=512, blank=True) created_at = models.DateTimeField(auto_now_add=True) diff --git a/backend/myproject/announcements/schemas.py b/backend/myproject/announcements/schemas.py new file mode 100644 index 0000000..fa44630 --- /dev/null +++ b/backend/myproject/announcements/schemas.py @@ -0,0 +1,29 @@ +from ninja import Schema +from typing import Optional +from datetime import datetime + + +class PostSchema(Schema): + id: int + title: str + description: str + created_at: datetime + created_by: int + + @classmethod + def from_orm(cls, obj): + return cls( + id=obj.id, + title=obj.title, + description=obj.description, + created_at=obj.created_at, + created_by=obj.created_by.id, + ) + +class PostCreateSchema(Schema): + title: str + description: Optional[str] = "" + +class PostUpdateSchema(Schema): + title: Optional[str] = None + description: Optional[str] = None diff --git a/backend/myproject/announcements/services.py b/backend/myproject/announcements/services.py index e69de29..bc3d35e 100644 --- a/backend/myproject/announcements/services.py +++ b/backend/myproject/announcements/services.py @@ -0,0 +1,42 @@ +from django.shortcuts import get_object_or_404 +from .models import PostModel +from ninja.errors import HttpError + +class AnnouncementService: + def _check_admin(self, request): + if request.user.role != "admin": + raise HttpError(401, "Unauthorized: Admin access required.") + return None + + def create_post(self, request, payload): + post = PostModel.objects.create( + title=payload.title, + description=payload.description, + created_by=request.user + ) + return post + + def list_posts(self): + posts = PostModel.objects.all().order_by("created_at") + result = [ + { + "id": post.id, + "title": post.title, + "description": post.description, + "created_at": post.created_at.isoformat(), + "created_by": post.created_by.id, + } + for post in posts + ] + return result + + def update_post(self, post, payload): + if payload.title is not None: + post.title = payload.title + if payload.description is not None: + post.description = payload.description + post.save() + return post + + def delete_post(self, post): + post.delete() diff --git a/backend/myproject/db.sqlite3 b/backend/myproject/db.sqlite3 index 9be5815..61ada48 100644 Binary files a/backend/myproject/db.sqlite3 and b/backend/myproject/db.sqlite3 differ diff --git a/backend/myproject/manage.py b/backend/myproject/manage.py index 222f06d..79b729e 100644 --- a/backend/myproject/manage.py +++ b/backend/myproject/manage.py @@ -2,11 +2,20 @@ """Django's command-line utility for administrative tasks.""" import os import sys +import debugpy + # os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') def main(): """Run administrative tasks.""" os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') + + # if "runserver" in sys.argv: + # import debugpy + # debugpy.listen(("0.0.0.0", 9000)) + # print("Waiting for debugger attach...") + # debugpy.wait_for_client() + try: from django.core.management import execute_from_command_line except ImportError as exc: diff --git a/backend/myproject/members/__pycache__/__init__.cpython-313.pyc b/backend/myproject/members/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..cc45b8d Binary files /dev/null and b/backend/myproject/members/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend/myproject/members/__pycache__/admin.cpython-313.pyc b/backend/myproject/members/__pycache__/admin.cpython-313.pyc new file mode 100644 index 0000000..80ee687 Binary files /dev/null and b/backend/myproject/members/__pycache__/admin.cpython-313.pyc differ diff --git a/backend/myproject/members/__pycache__/api.cpython-313.pyc b/backend/myproject/members/__pycache__/api.cpython-313.pyc new file mode 100644 index 0000000..f8bd8b0 Binary files /dev/null and b/backend/myproject/members/__pycache__/api.cpython-313.pyc differ diff --git a/backend/myproject/members/__pycache__/apps.cpython-313.pyc b/backend/myproject/members/__pycache__/apps.cpython-313.pyc new file mode 100644 index 0000000..f544a45 Binary files /dev/null and b/backend/myproject/members/__pycache__/apps.cpython-313.pyc differ diff --git a/backend/myproject/members/__pycache__/managers.cpython-313.pyc b/backend/myproject/members/__pycache__/managers.cpython-313.pyc new file mode 100644 index 0000000..e017f28 Binary files /dev/null and b/backend/myproject/members/__pycache__/managers.cpython-313.pyc differ diff --git a/backend/myproject/members/__pycache__/models.cpython-313.pyc b/backend/myproject/members/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000..3b837cc Binary files /dev/null and b/backend/myproject/members/__pycache__/models.cpython-313.pyc differ diff --git a/backend/myproject/members/__pycache__/schemas.cpython-313.pyc b/backend/myproject/members/__pycache__/schemas.cpython-313.pyc new file mode 100644 index 0000000..e792100 Binary files /dev/null and b/backend/myproject/members/__pycache__/schemas.cpython-313.pyc differ diff --git a/backend/myproject/members/__pycache__/services.cpython-313.pyc b/backend/myproject/members/__pycache__/services.cpython-313.pyc new file mode 100644 index 0000000..aa1053e Binary files /dev/null and b/backend/myproject/members/__pycache__/services.cpython-313.pyc differ diff --git a/backend/myproject/members/admin.py b/backend/myproject/members/admin.py index 8c38f3f..a75af42 100644 --- a/backend/myproject/members/admin.py +++ b/backend/myproject/members/admin.py @@ -1,3 +1,9 @@ from django.contrib import admin -# Register your models here. + +from .models import CustomUser + +@admin.register(CustomUser) +class CustomUserAdmin(admin.ModelAdmin): + list_display = ("email", "first_name", "last_name", "srn", "is_active", "is_staff") + search_fields = ("email", "srn") \ No newline at end of file diff --git a/backend/myproject/members/api.py b/backend/myproject/members/api.py index 5d3f0c3..61719fc 100644 --- a/backend/myproject/members/api.py +++ b/backend/myproject/members/api.py @@ -1,106 +1,58 @@ -from django.contrib.auth import authenticate, logout -from .models import CustomUser as User -from . import schemas from django.http import JsonResponse +from ninja_extra import NinjaExtraAPI, ControllerBase, api_controller, route +from ninja import UploadedFile from django.shortcuts import get_object_or_404 -from ninja_jwt.controller import NinjaJWTDefaultController -from ninja_jwt.authentication import JWTAuth -from ninja_jwt.tokens import RefreshToken -from ninja_extra import NinjaExtraAPI, ControllerBase, api_controller, route +from django.middleware.csrf import get_token +from . import schemas +from .models import CustomUser as User +from .services import UserAuthService from projects.api import ProjectsAPI +from announcements.api import PostAPIController +from registrations.api import ProjectApplicationAPI - - -@api_controller("/user", tags="User Authentication", auth=JWTAuth()) +@api_controller("/user", tags="User Authentication") class UserAuthAPI(ControllerBase): + def __init__(self) -> None: + self.auth_service = UserAuthService() - #API call for user to login by giving username and password. JWT based session will be created after this. - @route.post("/login", url_name="User login", auth=None) + # API call for user to login by giving username and password. + @route.post("/login", url_name="User login", auth=None, response=schemas.LoginResponseSchema) def login_view(self, request, payload: schemas.SignInSchema): - user = authenticate(request, username=payload.email, password=payload.password) - if user is not None: - refresh= RefreshToken.for_user(user) - return { - 'refresh': str(refresh), - 'access': str(refresh.access_token) - } + result = self.auth_service.login_user(request, payload.email, payload.password) + if result: + return result return JsonResponse({"detail": "Invalid credentials"}, status=401) - - #API call made by a user to logout., auth=JWTAuth() - @route.post("/logout", auth=None) + # API call made by a user to logout. + @route.post("/logout") def logout_view(self, request): - logout(request) - return {"message": "Logged out"} - - - #API call made by user to view their profile. - @route.get("/user", auth=JWTAuth()) - def user(self, request): - return { - "username": request.user.username, - "email": request.user.email, - "github": request.user.github, - "fname" : request.user.fname, - } - - - #API post request for a new user to create a new account. - @route.post("/register", auth=None) - def register(self, request, payload: schemas.RegisterSchema): - try: - user = User.objects.create_user( - username=payload.email, - email=payload.email, - password=payload.password, - first_name= payload.first_name, - last_name= payload.last_name, - ) - refresh = RefreshToken.for_user(user) - return { - "refresh": str(refresh), - "access": str(refresh.access_token), - } - except Exception as e: - return JsonResponse({"error": str(e)}, status=400) - - - - @route.put("/edit", auth=JWTAuth() ,response=schemas.RegisterSchema) - def edit_profile(self, request, payload : schemas.RegisterSchema): - user= get_object_or_404(User, srn=request.user.srn) - for attr, value in payload.dict().items(): - setattr(user, attr, value) - user.save() + return self.auth_service.logout_user(request) + + # API call made by user to view their profile. + @route.get("/user", response=schemas.UserProfileResponseSchema) + def user_profile(self, request): + result = self.auth_service.get_user_profile(request) + if result: + return result + return JsonResponse({"detail": "Not logged in"}, status=401) + + # API call made by user to edit their profile. + @route.put("/edit", response=schemas.RegisterSchema) + def edit_profile(self, request, payload: schemas.RegisterSchema): + if not request.user.is_authenticated: + return JsonResponse({"detail": "Authentication required"}, status=401) + user = self.auth_service.edit_user_profile(request, payload) return user - - - + # API call to import users using the management command. + @route.post("/import-users/", url_name="Import Users") + def import_users(self, request, file: UploadedFile): + return self.auth_service.import_users_from_file(file) +# Register controllers with the NinjaExtraAPI instance. api = NinjaExtraAPI(csrf=False) -api.register_controllers(NinjaJWTDefaultController) api.register_controllers(UserAuthAPI) +api.register_controllers(PostAPIController) api.register_controllers(ProjectsAPI) - - - - - - - - - - - - - - - - - - - - - +api.register_controllers(ProjectApplicationAPI) diff --git a/backend/myproject/members/management/__init__.py b/backend/myproject/members/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/myproject/members/management/__pycache__/__init__.cpython-313.pyc b/backend/myproject/members/management/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..0432021 Binary files /dev/null and b/backend/myproject/members/management/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend/myproject/members/management/commands/__init__.py b/backend/myproject/members/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/myproject/members/management/commands/__pycache__/__init__.cpython-313.pyc b/backend/myproject/members/management/commands/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..fa2910f Binary files /dev/null and b/backend/myproject/members/management/commands/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend/myproject/members/management/commands/__pycache__/import_users.cpython-313.pyc b/backend/myproject/members/management/commands/__pycache__/import_users.cpython-313.pyc new file mode 100644 index 0000000..72f3d5b Binary files /dev/null and b/backend/myproject/members/management/commands/__pycache__/import_users.cpython-313.pyc differ diff --git a/backend/myproject/members/management/commands/import_users.py b/backend/myproject/members/management/commands/import_users.py new file mode 100644 index 0000000..7732ed3 --- /dev/null +++ b/backend/myproject/members/management/commands/import_users.py @@ -0,0 +1,81 @@ +import csv +import os +from django.core.management.base import BaseCommand +from members.models import CustomUser +from django.contrib.auth.hashers import make_password +from django.utils.text import slugify + +class Command(BaseCommand): + help = "Import users from a CSV file and create them in the database" + + def add_arguments(self, parser): + parser.add_argument("file_path", type=str, help="Path to the CSV file") + + def generate_unique_username(self, email): + """Generate a unique username based on email.""" + base_username = slugify(email.split('@')[0]) # Convert to a slug + username = base_username + counter = 1 + + while CustomUser.objects.filter(username=username).exists(): + username = f"{base_username}{counter}" + counter += 1 + + return username + + def handle(self, *args, **kwargs): + file_path = kwargs["file_path"] + users_created = 0 + skipped_users = 0 + + try: + with open(file_path, newline='', encoding='utf-8') as csvfile: + reader = csv.DictReader(csvfile) + + for row in reader: + email = row.get("email", "").strip() + + # Check if user already exists + if CustomUser.objects.filter(email=email).exists(): + print(f"Skipping {email} (User already exists)") + skipped_users += 1 + continue + + print(f"Creating user: {email}") + + # Ensure unique username + username = row.get("username", "").strip() + if not username: + username = self.generate_unique_username(email) + + # Create new user + user = CustomUser( + email=email, + username=username, + first_name=row.get("first_name", "").strip(), + last_name=row.get("last_name", "").strip(), + phone=row.get("phone", "").strip(), + srn=row.get("srn", "").strip(), + branch=row.get("branch", "").strip(), + semester=row.get("semester", "1").strip(), + github=row.get("github", "").strip(), + linkedin=row.get("linkedin", "").strip(), + role="student", + ) + + user.set_password("test@123") # Set default password + + print(f"Saving user: {user.email}") + try: + user.save() + print(f"User {user.email} saved successfully!") + users_created += 1 + self.stdout.write(self.style.SUCCESS(f"User {email} created successfully!")) + except Exception as e: + print(f"Error saving user {user.email}: {e}") + + except FileNotFoundError: + self.stderr.write(self.style.ERROR(f"File {file_path} not found!")) + return + + self.stdout.write(self.style.SUCCESS(f"Import completed: {users_created} new users, {skipped_users} skipped.")) diff --git a/backend/myproject/members/managers.py b/backend/myproject/members/managers.py new file mode 100644 index 0000000..2c8ca01 --- /dev/null +++ b/backend/myproject/members/managers.py @@ -0,0 +1,15 @@ +from django.contrib.auth.models import BaseUserManager + +class CustomUserManager(BaseUserManager): + def create_user(self, email, password=None, **extra_fields): + if not email: + raise ValueError('The Email field must be set') + email = self.normalize_email(email) + user = self.model(email=email, **extra_fields) + user.set_password(password) + user.save(using=self._db) + return user + + def create_superuser(self, email, password=None, **extra_fields): + extra_fields.setdefault('role', 'admin') + return self.create_user(email, password, **extra_fields) diff --git a/backend/myproject/members/migrations/0001_initial.py b/backend/myproject/members/migrations/0001_initial.py index 215912b..785f258 100644 --- a/backend/myproject/members/migrations/0001_initial.py +++ b/backend/myproject/members/migrations/0001_initial.py @@ -1,6 +1,5 @@ -# Generated by Django 5.0.7 on 2024-08-10 13:33 +# Generated by Django 5.1.6 on 2025-02-18 19:17 -import django.contrib.auth.models import django.contrib.auth.validators import django.utils.timezone from django.db import migrations, models @@ -11,10 +10,22 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('auth', '0013_alter_user_email'), + ('auth', '0012_alter_user_first_name_max_length'), ] operations = [ + migrations.CreateModel( + name='FileModel', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200, unique=True)), + ('type', models.CharField(choices=[('PD', 'pdf'), ('IM', 'image'), ('LI', 'link')], max_length=2)), + ('file', models.FileField(blank=True, null=True, upload_to='files/')), + ('image', models.ImageField(blank=True, null=True, upload_to='images/')), + ('url', models.URLField(blank=True, max_length=500, null=True)), + ('uploaded_at', models.DateTimeField(auto_now_add=True, db_index=True)), + ], + ), migrations.CreateModel( name='CustomUser', fields=[ @@ -25,17 +36,19 @@ class Migration(migrations.Migration): ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), - ('email', models.EmailField(blank=True, max_length=254, unique=True, verbose_name='email address')), ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), - ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), - ('alt_email', models.EmailField(max_length=254, unique=True)), + ('email', models.EmailField(max_length=254, unique=True)), ('phone', models.CharField(max_length=10)), - ('srn', models.CharField(max_length=8, unique=True)), + ('srn', models.CharField(max_length=8)), ('branch', models.CharField(max_length=50)), ('semester', models.CharField(default='1', max_length=2)), - ('github', models.URLField(default='')), - ('linkedin', models.URLField(default='')), + ('github', models.URLField(blank=True, default='')), + ('linkedin', models.URLField(blank=True, default='')), + ('is_active', models.BooleanField(default=True)), + ('otp_token', models.CharField(blank=True, max_length=6, null=True)), + ('otp_expiration', models.DateTimeField(blank=True, null=True)), + ('role', models.CharField(choices=[('admin', 'Admin'), ('student', 'Student')], default='student', max_length=10)), ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), ], @@ -44,8 +57,5 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'users', 'abstract': False, }, - managers=[ - ('objects', django.contrib.auth.models.UserManager()), - ], ), ] diff --git a/backend/myproject/members/migrations/0002_alter_customuser_srn.py b/backend/myproject/members/migrations/0002_alter_customuser_srn.py deleted file mode 100644 index 3306fd2..0000000 --- a/backend/myproject/members/migrations/0002_alter_customuser_srn.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.0.7 on 2024-08-12 05:54 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('members', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='customuser', - name='srn', - field=models.CharField(max_length=8), - ), - ] diff --git a/backend/myproject/members/migrations/0005_filemodel_related_project_and_more.py b/backend/myproject/members/migrations/0002_initial.py similarity index 91% rename from backend/myproject/members/migrations/0005_filemodel_related_project_and_more.py rename to backend/myproject/members/migrations/0002_initial.py index 132d855..a31271d 100644 --- a/backend/myproject/members/migrations/0005_filemodel_related_project_and_more.py +++ b/backend/myproject/members/migrations/0002_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.7 on 2024-09-03 16:35 +# Generated by Django 5.1.6 on 2025-02-18 19:17 import django.db.models.deletion from django.conf import settings @@ -7,8 +7,10 @@ class Migration(migrations.Migration): + initial = True + dependencies = [ - ('members', '0004_filemodel_alter_customuser_email_and_more'), + ('members', '0001_initial'), ('projects', '0001_initial'), ] diff --git a/backend/myproject/members/migrations/0003_remove_customuser_alt_email.py b/backend/myproject/members/migrations/0003_remove_customuser_alt_email.py deleted file mode 100644 index 7b9e239..0000000 --- a/backend/myproject/members/migrations/0003_remove_customuser_alt_email.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.0.7 on 2024-08-12 06:09 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('members', '0002_alter_customuser_srn'), - ] - - operations = [ - migrations.RemoveField( - model_name='customuser', - name='alt_email', - ), - ] diff --git a/backend/myproject/members/migrations/0004_filemodel_alter_customuser_email_and_more.py b/backend/myproject/members/migrations/0004_filemodel_alter_customuser_email_and_more.py deleted file mode 100644 index 78636b4..0000000 --- a/backend/myproject/members/migrations/0004_filemodel_alter_customuser_email_and_more.py +++ /dev/null @@ -1,40 +0,0 @@ -# Generated by Django 5.0.7 on 2024-09-03 16:35 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('members', '0003_remove_customuser_alt_email'), - ] - - operations = [ - migrations.CreateModel( - name='FileModel', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200, unique=True)), - ('type', models.CharField(choices=[('PD', 'pdf'), ('IM', 'image'), ('LI', 'link')], max_length=2)), - ('file', models.FileField(blank=True, null=True, upload_to='files/')), - ('image', models.ImageField(blank=True, null=True, upload_to='images/')), - ('url', models.URLField(blank=True, max_length=500, null=True)), - ('uploaded_at', models.DateTimeField(auto_now_add=True, db_index=True)), - ], - ), - migrations.AlterField( - model_name='customuser', - name='email', - field=models.EmailField(max_length=254, unique=True), - ), - migrations.AlterField( - model_name='customuser', - name='github', - field=models.URLField(blank=True, default=''), - ), - migrations.AlterField( - model_name='customuser', - name='linkedin', - field=models.URLField(blank=True, default=''), - ), - ] diff --git a/backend/myproject/members/migrations/__pycache__/0001_initial.cpython-313.pyc b/backend/myproject/members/migrations/__pycache__/0001_initial.cpython-313.pyc new file mode 100644 index 0000000..0760654 Binary files /dev/null and b/backend/myproject/members/migrations/__pycache__/0001_initial.cpython-313.pyc differ diff --git a/backend/myproject/members/migrations/__pycache__/0002_initial.cpython-313.pyc b/backend/myproject/members/migrations/__pycache__/0002_initial.cpython-313.pyc new file mode 100644 index 0000000..d761016 Binary files /dev/null and b/backend/myproject/members/migrations/__pycache__/0002_initial.cpython-313.pyc differ diff --git a/backend/myproject/members/migrations/__pycache__/__init__.cpython-313.pyc b/backend/myproject/members/migrations/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..79ba7a0 Binary files /dev/null and b/backend/myproject/members/migrations/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend/myproject/members/models.py b/backend/myproject/members/models.py index 347b0ae..385524d 100644 --- a/backend/myproject/members/models.py +++ b/backend/myproject/members/models.py @@ -2,6 +2,7 @@ from django.contrib.auth.models import AbstractUser from django.db import models from pydantic import ValidationError +from .managers import CustomUserManager from django.core.exceptions import ValidationError @@ -24,6 +25,18 @@ class CustomUser(AbstractUser): semester = models.CharField(max_length=2, default='1') github = models.URLField(default='', blank=True) linkedin = models.URLField(default='', blank=True) + is_active = models.BooleanField(default=True) + otp_token = models.CharField(max_length=6, blank=True, null=True) + otp_expiration = models.DateTimeField(null=True, blank=True) + + + + ROLE_CHOICES = [ + ('admin', 'Admin'), + ('student', 'Student') + ] + role = models.CharField(max_length=10, choices=ROLE_CHOICES, default='student') + objects = CustomUserManager() USERNAME_FIELD = 'email' REQUIRED_FIELDS = ['username', 'first_name', 'last_name', 'phone', 'srn', 'branch', 'semester'] diff --git a/backend/myproject/members/schemas.py b/backend/myproject/members/schemas.py index 9510f36..7686249 100644 --- a/backend/myproject/members/schemas.py +++ b/backend/myproject/members/schemas.py @@ -9,6 +9,8 @@ class SignInSchema(BaseModel): email: str password: str + model_config = {"arbitrary_types_allowed": True} + class RegisterSchema(ModelSchema): class Meta: @@ -19,4 +21,24 @@ class Meta: 'last_name', 'password', 'srn', - ] \ No newline at end of file + ] + + model_config = {"arbitrary_types_allowed": True} + +class LoginResponseSchema(Schema): + message: str + role: str + csrf_token: str + + model_config = { + "arbitrary_types_allowed": True, + } + + +class UserProfileResponseSchema(Schema): + username: str + email: str + github: str + fname: str + + model_config = {"arbitrary_types_allowed": True} \ No newline at end of file diff --git a/backend/myproject/members/services.py b/backend/myproject/members/services.py index e69de29..15290e4 100644 --- a/backend/myproject/members/services.py +++ b/backend/myproject/members/services.py @@ -0,0 +1,56 @@ +import os +from django.contrib.auth import authenticate, logout, login +from django.core.management import call_command +from django.shortcuts import get_object_or_404 +from django.http import JsonResponse +from django.middleware.csrf import get_token +from django.core.files.base import ContentFile +from django.core.files.storage import default_storage +from .models import CustomUser as User + +class UserAuthService: + def login_user(self, request, email, password): + user = authenticate(request, username=email, password=password) + if user is not None: + login(request, user) + return { + "message": "Login successful", + "role": user.role, + "csrf_token": get_token(request) + } + return None # caller can then return an error response + + def logout_user(self, request): + logout(request) + return {"message": "Logged out successfully"} + + def get_user_profile(self, request): + if request.user.is_authenticated: + return { + "username": request.user.username, + "email": request.user.email, + "github": request.user.github, + "first_name": request.user.first_name, + } + return None + + def edit_user_profile(self, request, payload): + if not request.user.is_authenticated: + return None + user = get_object_or_404(User, srn=request.user.srn) + for attr, value in payload.dict().items(): + setattr(user, attr, value) + user.save() + return user + + def import_users_from_file(self, file): + file_path = default_storage.save(f"temp/{file.name}", ContentFile(file.read())) + try: + call_command("import_users", default_storage.path(file_path)) + except Exception as e: + return {"error": str(e)} + finally: + full_path = default_storage.path(file_path) + if os.path.exists(full_path): + os.remove(full_path) + return {"message": "User import initiated successfully!"} diff --git a/backend/myproject/myproject/__pycache__/__init__.cpython-313.pyc b/backend/myproject/myproject/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..19b8cc0 Binary files /dev/null and b/backend/myproject/myproject/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend/myproject/myproject/__pycache__/settings.cpython-313.pyc b/backend/myproject/myproject/__pycache__/settings.cpython-313.pyc new file mode 100644 index 0000000..af82dc6 Binary files /dev/null and b/backend/myproject/myproject/__pycache__/settings.cpython-313.pyc differ diff --git a/backend/myproject/myproject/__pycache__/urls.cpython-313.pyc b/backend/myproject/myproject/__pycache__/urls.cpython-313.pyc new file mode 100644 index 0000000..3d732e1 Binary files /dev/null and b/backend/myproject/myproject/__pycache__/urls.cpython-313.pyc differ diff --git a/backend/myproject/myproject/__pycache__/wsgi.cpython-313.pyc b/backend/myproject/myproject/__pycache__/wsgi.cpython-313.pyc new file mode 100644 index 0000000..0ec85ce Binary files /dev/null and b/backend/myproject/myproject/__pycache__/wsgi.cpython-313.pyc differ diff --git a/backend/myproject/myproject/settings.py b/backend/myproject/myproject/settings.py index eb8fb47..a91eeb5 100644 --- a/backend/myproject/myproject/settings.py +++ b/backend/myproject/myproject/settings.py @@ -11,6 +11,11 @@ """ import os from pathlib import Path + +from dotenv import load_dotenv + +load_dotenv() + # import django_heroku # import dj_database_url # import os @@ -28,30 +33,14 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [] - - -from datetime import timedelta -#Django-ninja jwt settings -NINJA_JWT = { - 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), - 'REFRESH_TOKEN_LIFETIME': timedelta(days=1), - 'ROTATE_REFRESH_TOKENS': True, - 'BLACKLIST_AFTER_ROTATION': True, - 'ALGORITHM': 'HS256', - 'SIGNING_KEY': SECRET_KEY, - 'VERIFYING_KEY': None, - 'AUTH_HEADER_TYPES': ('Bearer',), - 'USER_ID_FIELD': 'id', - 'USER_ID_CLAIM': 'user_id', -} +ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "127.0.0.1,localhost").split(",") # Application definition INSTALLED_APPS = [ - # 'django.contrib.admin', + 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', @@ -60,13 +49,23 @@ 'members', 'ninja', 'corsheaders', - 'ninja_jwt', 'ninja_extra', 'projects', 'announcements', 'registrations', + ] +AUTHENTICATION_BACKENDS = [ + "django.contrib.auth.backends.ModelBackend", +] + +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework.authentication.SessionAuthentication', + ] +} + MIDDLEWARE = [ 'whitenoise.middleware.WhiteNoiseMiddleware', "corsheaders.middleware.CorsMiddleware", @@ -104,15 +103,29 @@ # Database # https://docs.djangoproject.com/en/5.0/ref/settings/#databases -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', +if os.getenv('DJANGO_ENV') == 'production': + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': os.getenv('DB_NAME', 'postgres'), + 'USER': os.getenv('DB_USER', 'postgres'), + 'PASSWORD': os.getenv('DB_PASSWORD', ''), + 'HOST': os.getenv('DB_HOST', 'localhost'), + 'PORT': int(os.getenv('DB_PORT', 6543)), # Cast to integer + 'OPTIONS': {'sslmode': 'require'}, + } + } +else: + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', # Use Pathlib for cross-platform support + } } -} -# if 'DATABASE_URL' in os.environ: -# DATABASES['default'] = dj_database_url.config(default=os.environ['DATABASE_URL']) +import dj_database_url +if os.getenv('DJANGO_ENV') == 'production' and 'DATABASE_URL' in os.environ: + DATABASES['default'] = dj_database_url.config(default=os.environ['DATABASE_URL']) # Password validation # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators @@ -161,11 +174,32 @@ CORS_ALLOW_CREDENTIALS = True +CSRF_COOKIE_NAME = "csrftoken" +CSRF_COOKIE_HTTPONLY = False CORS_ALLOWED_ORIGINS = ["http://localhost:8000"] -CSRF_TRUSTED_ORIGINS = ['http://localhost:8000'] +CSRF_TRUSTED_ORIGINS = ["https://dev-track-app.onrender.com/", 'http://localhost:8000'] +CSRF_USE_SESSIONS = True AUTH_USER_MODEL = 'members.CustomUser' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') -MEDIA_URL = '/media/' \ No newline at end of file +MEDIA_URL = '/media/' + + +# database-backed sessions +SESSION_ENGINE = "django.contrib.sessions.backends.db" + +# Name of the session cookie +SESSION_COOKIE_NAME = "sessionid" + +# Ensure the session expires when the browser is closed +SESSION_EXPIRE_AT_BROWSER_CLOSE = False + +# Set session age (e.g., 2 weeks) +SESSION_COOKIE_AGE = 1209600 # 2 weeks in seconds + +# Secure session settings for production +SESSION_COOKIE_SECURE = False # Set to True in production if using HTTPS +SESSION_COOKIE_HTTPONLY = True +SESSION_COOKIE_SAMESITE = "Lax" # Change to 'None' if working with cross-site requests diff --git a/backend/myproject/myproject/urls.py b/backend/myproject/myproject/urls.py index 91f8e51..7a08a29 100644 --- a/backend/myproject/myproject/urls.py +++ b/backend/myproject/myproject/urls.py @@ -6,6 +6,6 @@ urlpatterns = [ - # path('admin/', admin.site.urls), + path('admin/', admin.site.urls), path("api/", api.urls), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/backend/myproject/projects/__pycache__/__init__.cpython-313.pyc b/backend/myproject/projects/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..74b2bae Binary files /dev/null and b/backend/myproject/projects/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend/myproject/projects/__pycache__/admin.cpython-313.pyc b/backend/myproject/projects/__pycache__/admin.cpython-313.pyc new file mode 100644 index 0000000..02e8454 Binary files /dev/null and b/backend/myproject/projects/__pycache__/admin.cpython-313.pyc differ diff --git a/backend/myproject/projects/__pycache__/api.cpython-313.pyc b/backend/myproject/projects/__pycache__/api.cpython-313.pyc new file mode 100644 index 0000000..0464f5b Binary files /dev/null and b/backend/myproject/projects/__pycache__/api.cpython-313.pyc differ diff --git a/backend/myproject/projects/__pycache__/apps.cpython-313.pyc b/backend/myproject/projects/__pycache__/apps.cpython-313.pyc new file mode 100644 index 0000000..60ce407 Binary files /dev/null and b/backend/myproject/projects/__pycache__/apps.cpython-313.pyc differ diff --git a/backend/myproject/projects/__pycache__/models.cpython-313.pyc b/backend/myproject/projects/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000..f15e2d6 Binary files /dev/null and b/backend/myproject/projects/__pycache__/models.cpython-313.pyc differ diff --git a/backend/myproject/projects/__pycache__/schemas.cpython-313.pyc b/backend/myproject/projects/__pycache__/schemas.cpython-313.pyc new file mode 100644 index 0000000..7fcbd29 Binary files /dev/null and b/backend/myproject/projects/__pycache__/schemas.cpython-313.pyc differ diff --git a/backend/myproject/projects/__pycache__/services.cpython-313.pyc b/backend/myproject/projects/__pycache__/services.cpython-313.pyc new file mode 100644 index 0000000..5f503d2 Binary files /dev/null and b/backend/myproject/projects/__pycache__/services.cpython-313.pyc differ diff --git a/backend/myproject/projects/api.py b/backend/myproject/projects/api.py index 5b569f4..48f87a9 100644 --- a/backend/myproject/projects/api.py +++ b/backend/myproject/projects/api.py @@ -40,33 +40,39 @@ def list_domain(self, request): return domain_list except Exception as e: raise HttpError(400, str(e)) + + #API call to create a new project cycle. + @route.post("/cycle/create", url_name="Create project cycle") + def create_project_cycle(self, request, payload:schemas.CreateProjectCycleSchema): + return self.project_service.create_project_cycle(request, payload) - #API call to create a new project - @route.post("/create", url_name="Create project") - def create_project(self, payload:schemas.CreateProjectSchema, file: UploadedFile = File(...)): - response = self.project_service.create_project(payload,file) - if 'error' in response: - raise HttpError(400, response['error']) - return {"message": "Project created successfully", "project_id": response['project_id']} + # #API call to create a new project + # @route.post("/create", url_name="Create project") + # def create_project(self, payload:schemas.CreateProjectSchema, file: UploadedFile = File(...)): + # response = self.project_service.create_project(payload,file) + # if 'error' in response: + # raise HttpError(400, response['error']) + # return {"message": "Project created successfully", "project_id": response['project_id']} - #Api to list all the names of the projects. - @route.get("/list", url_name="List Projects", response=list[ListProjectSchema]) - def list_projects(self,request): - try: - projects = ProjectModel.objects.all() - project_list = [ - ListProjectSchema( - name=project.name, - domain=project.domain.name, - created_at=project.created_at.isoformat() - ) - for project in projects - ] - return project_list - except Exception as e: - raise HttpError(400, str(e)) + + # #Api to list all the names of the projects. + # @route.get("/list", url_name="List Projects", response=list[ListProjectSchema]) + # def list_projects(self,request): + # try: + # projects = ProjectModel.objects.all() + # project_list = [ + # ListProjectSchema( + # name=project.name, + # domain=project.domain.name, + # created_at=project.created_at.isoformat() + # ) + # for project in projects + # ] + # return project_list + # except Exception as e: + # raise HttpError(400, str(e)) diff --git a/backend/myproject/projects/migrations/0001_initial.py b/backend/myproject/projects/migrations/0001_initial.py index 58a0239..0792814 100644 --- a/backend/myproject/projects/migrations/0001_initial.py +++ b/backend/myproject/projects/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.7 on 2024-09-03 16:35 +# Generated by Django 5.1.6 on 2025-02-18 19:17 import django.db.models.deletion from django.conf import settings @@ -10,7 +10,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('members', '0004_filemodel_alter_customuser_email_and_more'), + ('members', '0001_initial'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] diff --git a/backend/myproject/projects/migrations/0002_alter_projectmodel_description_and_more.py b/backend/myproject/projects/migrations/0002_alter_projectmodel_description_and_more.py deleted file mode 100644 index 4b86281..0000000 --- a/backend/myproject/projects/migrations/0002_alter_projectmodel_description_and_more.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 5.0.7 on 2024-09-03 18:34 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('projects', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='projectmodel', - name='description', - field=models.TextField(blank=True, max_length=256), - ), - migrations.AlterField( - model_name='projectmodel', - name='status', - field=models.CharField(choices=[('ON', 'ongoing'), ('PA', 'past')], default='PA', max_length=100), - ), - ] diff --git a/backend/myproject/projects/migrations/0003_alter_projectmodel_description_and_more.py b/backend/myproject/projects/migrations/0003_alter_projectmodel_description_and_more.py deleted file mode 100644 index 4812d2b..0000000 --- a/backend/myproject/projects/migrations/0003_alter_projectmodel_description_and_more.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 5.0.7 on 2024-09-04 03:01 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('projects', '0002_alter_projectmodel_description_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='projectmodel', - name='description', - field=models.TextField(max_length=256), - ), - migrations.AlterField( - model_name='projectmodel', - name='status', - field=models.CharField(choices=[('ON', 'ongoing'), ('PA', 'past')], max_length=100), - ), - ] diff --git a/backend/myproject/projects/migrations/__pycache__/0001_initial.cpython-313.pyc b/backend/myproject/projects/migrations/__pycache__/0001_initial.cpython-313.pyc new file mode 100644 index 0000000..36f6290 Binary files /dev/null and b/backend/myproject/projects/migrations/__pycache__/0001_initial.cpython-313.pyc differ diff --git a/backend/myproject/projects/migrations/__pycache__/__init__.cpython-313.pyc b/backend/myproject/projects/migrations/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..f2000c1 Binary files /dev/null and b/backend/myproject/projects/migrations/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend/myproject/projects/schemas.py b/backend/myproject/projects/schemas.py index 314f299..2ae75dc 100644 --- a/backend/myproject/projects/schemas.py +++ b/backend/myproject/projects/schemas.py @@ -1,5 +1,5 @@ from ninja import ModelSchema -from pydantic import BaseModel +from pydantic import BaseModel, Field from ninja import Schema from projects.models import ProjectModel, DomainModel @@ -27,3 +27,10 @@ class CreateProjectSchema(Schema): youtube_link : str +class CreateProjectCycleSchema(Schema): + cycle_name : str + start_date : str + end_date : str + is_active : bool = Field(default=False) + + diff --git a/backend/myproject/projects/services.py b/backend/myproject/projects/services.py index d4673d0..104a566 100644 --- a/backend/myproject/projects/services.py +++ b/backend/myproject/projects/services.py @@ -3,7 +3,8 @@ from .models import ProjectModel from .schemas import CreateProjectSchema, ListProjectSchema, CreateDomainSchema, ListDomainSchema from members.models import FileModel -from projects.models import DomainModel +from projects.models import DomainModel, ProjectCycleModel +from projects.schemas import CreateProjectCycleSchema, CreateDomainSchema class ProjectService: @@ -40,6 +41,19 @@ def create_project(self, details : CreateProjectSchema, pdf_file): return {"error": str(e)} + def create_project_cycle(self, payload : CreateProjectCycleSchema): + try: + response = ProjectCycleModel.objects.create( + cycle_name = payload.cycle_name, + start_date = payload.start_date, #Notes for frontend : Dates will be in the format : + end_date = payload.end_date, #ISO 8601 format : YYYY-MM-DD + is_active = payload.is_active + ) + return {"Success":"Project cycle has been created"} + except Exception as e: + return {"error": str(e)} + + class DomainService: #Service to create a new domain diff --git a/backend/myproject/registrations/__pycache__/__init__.cpython-313.pyc b/backend/myproject/registrations/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..a913c18 Binary files /dev/null and b/backend/myproject/registrations/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend/myproject/registrations/__pycache__/admin.cpython-313.pyc b/backend/myproject/registrations/__pycache__/admin.cpython-313.pyc new file mode 100644 index 0000000..c7c69e8 Binary files /dev/null and b/backend/myproject/registrations/__pycache__/admin.cpython-313.pyc differ diff --git a/backend/myproject/registrations/__pycache__/api.cpython-313.pyc b/backend/myproject/registrations/__pycache__/api.cpython-313.pyc new file mode 100644 index 0000000..f0b7ae1 Binary files /dev/null and b/backend/myproject/registrations/__pycache__/api.cpython-313.pyc differ diff --git a/backend/myproject/registrations/__pycache__/apps.cpython-313.pyc b/backend/myproject/registrations/__pycache__/apps.cpython-313.pyc new file mode 100644 index 0000000..8780349 Binary files /dev/null and b/backend/myproject/registrations/__pycache__/apps.cpython-313.pyc differ diff --git a/backend/myproject/registrations/__pycache__/models.cpython-313.pyc b/backend/myproject/registrations/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000..f89f7d3 Binary files /dev/null and b/backend/myproject/registrations/__pycache__/models.cpython-313.pyc differ diff --git a/backend/myproject/registrations/__pycache__/schemas.cpython-313.pyc b/backend/myproject/registrations/__pycache__/schemas.cpython-313.pyc new file mode 100644 index 0000000..255a20a Binary files /dev/null and b/backend/myproject/registrations/__pycache__/schemas.cpython-313.pyc differ diff --git a/backend/myproject/registrations/__pycache__/services.cpython-313.pyc b/backend/myproject/registrations/__pycache__/services.cpython-313.pyc new file mode 100644 index 0000000..c24f3e6 Binary files /dev/null and b/backend/myproject/registrations/__pycache__/services.cpython-313.pyc differ diff --git a/backend/myproject/registrations/api.py b/backend/myproject/registrations/api.py index e69de29..b1fdd21 100644 --- a/backend/myproject/registrations/api.py +++ b/backend/myproject/registrations/api.py @@ -0,0 +1,15 @@ +from ninja import Router +from ninja_extra import ControllerBase, api_controller, route +from .schemas import ProjectApplicationSchema +from .services import ProjectApplicationService + +@api_controller("/applications", tags=["Project Applications"]) +class ProjectApplicationAPI(ControllerBase): + + def __init__(self) -> None: + self.application_service = ProjectApplicationService() + + @route.post("/enroll", url_name="Enroll in Project") + def enroll_now(self, request, payload: ProjectApplicationSchema): + response = self.application_service.enroll_user(request, payload) + return response diff --git a/backend/myproject/registrations/migrations/0001_initial.py b/backend/myproject/registrations/migrations/0001_initial.py index 946453b..c0177b3 100644 --- a/backend/myproject/registrations/migrations/0001_initial.py +++ b/backend/myproject/registrations/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.7 on 2024-09-03 16:35 +# Generated by Django 5.1.6 on 2025-02-18 19:17 import django.db.models.deletion from django.conf import settings diff --git a/backend/myproject/registrations/migrations/__pycache__/0001_initial.cpython-313.pyc b/backend/myproject/registrations/migrations/__pycache__/0001_initial.cpython-313.pyc new file mode 100644 index 0000000..3d57f41 Binary files /dev/null and b/backend/myproject/registrations/migrations/__pycache__/0001_initial.cpython-313.pyc differ diff --git a/backend/myproject/registrations/migrations/__pycache__/__init__.cpython-313.pyc b/backend/myproject/registrations/migrations/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..9c72f47 Binary files /dev/null and b/backend/myproject/registrations/migrations/__pycache__/__init__.cpython-313.pyc differ diff --git a/backend/myproject/registrations/schemas.py b/backend/myproject/registrations/schemas.py new file mode 100644 index 0000000..6d2df50 --- /dev/null +++ b/backend/myproject/registrations/schemas.py @@ -0,0 +1,6 @@ +from ninja import Schema +from typing import Optional + +class ProjectApplicationSchema(Schema): + first_preference_id: int + second_preference_id: int diff --git a/backend/myproject/registrations/services.py b/backend/myproject/registrations/services.py index e69de29..ff9d00a 100644 --- a/backend/myproject/registrations/services.py +++ b/backend/myproject/registrations/services.py @@ -0,0 +1,44 @@ +from django.shortcuts import get_object_or_404 +from projects.models import DomainModel, ProjectCycleModel +from .models import ProjectApplicationModel +from ninja.errors import HttpError + +class ProjectApplicationService: + def enroll_user(self, request, payload): + # Check if user is authenticated + if not request.user.is_authenticated: + raise HttpError(400, "Authentication required") + + user = request.user + + # Retrieve the current active project cycle + cycle = ProjectCycleModel.objects.filter(is_active=True).first() + if not cycle: + raise HttpError(400, "No active project cycle found.") + + if payload.first_preference_id == payload.second_preference_id: + raise HttpError(400, "First and second preference cannot be the same.") + + # Retrieve the domain choices + #Instructions for frontend : + # Send the domain id in the payload, + # But in the ui, show the domain name to the user + # This way, the user will see the domain name, but the backend will receive the domain id + first_pref = get_object_or_404(DomainModel, id=payload.first_preference_id) + second_pref = get_object_or_404(DomainModel, id=payload.second_preference_id) + + # Create or get the application + application, created = ProjectApplicationModel.objects.get_or_create( + user=user, + cycle=cycle, + defaults={ + "first_preference": first_pref, + "second_preference": second_pref, + "is_selected": False + } + ) + + if not created: + return {"message": "You have already applied for this cycle."} + + return {"message": "Application submitted successfully, hang tight!"} diff --git a/backend/myproject/requirements.txt b/backend/myproject/requirements.txt index beccf13..bd1795b 100644 --- a/backend/myproject/requirements.txt +++ b/backend/myproject/requirements.txt @@ -1,58 +1,29 @@ annotated-types==0.7.0 asgiref==3.8.1 -asttokens==2.4.1 -cffi==1.16.0 -colorama==0.4.6 -comm==0.2.2 +cffi==1.17.1 contextlib2==21.6.0 -cryptography==43.0.0 -debugpy==1.8.1 -decorator==5.1.1 -dj-database-url==2.2.0 -Django==5.0.7 -django-cors-headers==4.4.0 -django-enumfields==2.1.1 -django-heroku==0.3.1 -django-ninja==1.2.2 -django-ninja-extra==0.21.2 -django-ninja-jwt==5.3.2 -django-phonenumber-field==8.0.0 +cryptography==44.0.1 +debugpy==1.8.12 +dj-database-url==2.3.0 +Django==5.1.6 +django-cors-headers==4.7.0 +django-ninja==1.3.0 +django-ninja-extra==0.22.3 +django-ninja-jwt==5.3.5 djangorestframework==3.15.2 -djangorestframework-simplejwt==5.3.1 -executing==2.0.1 -gunicorn==22.0.0 +djangorestframework_simplejwt==5.4.0 +gunicorn==23.0.0 injector==0.22.0 -ipykernel==6.29.4 -ipython==8.24.0 -jedi==0.19.1 -jupyter_client==8.6.1 -jupyter_core==5.7.2 -matplotlib-inline==0.1.7 -nest-asyncio==1.6.0 -packaging==24.0 -parso==0.8.4 -phonenumberslite==8.13.42 -platformdirs==4.2.2 -prompt-toolkit==3.0.43 -psutil==5.9.8 -psycopg2==2.9.9 -pure-eval==0.2.2 +packaging==24.2 +pillow==11.1.0 +psycopg2==2.9.10 +psycopg2-binary==2.9.10 pycparser==2.22 -pydantic==2.8.2 -pydantic_core==2.20.1 -Pygments==2.18.0 -PyJWT==2.9.0 -python-dateutil==2.9.0.post0 -pywin32==306 -pyzmq==26.0.3 -setuptools==69.5.1 -six==1.16.0 -sqlparse==0.5.1 -stack-data==0.6.3 -tornado==6.4 -traitlets==5.14.3 +pydantic==2.10.6 +pydantic_core==2.27.2 +PyJWT==2.10.1 +python-dotenv==1.0.1 +sqlparse==0.5.3 typing_extensions==4.12.2 -tzdata==2024.1 -wcwidth==0.2.13 -wheel==0.43.0 -whitenoise==6.7.0 +tzdata==2025.1 +whitenoise==6.9.0 diff --git a/frontend/android/app/build.gradle b/frontend/android/app/build.gradle index 896fb11..7ff43b9 100644 --- a/frontend/android/app/build.gradle +++ b/frontend/android/app/build.gradle @@ -46,7 +46,7 @@ android { // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion + targetSdkVersion (flutter.targetSdkVersion - 1) versionCode flutterVersionCode.toInteger() versionName flutterVersionName } @@ -64,4 +64,7 @@ flutter { source '../..' } -dependencies {} +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0" + implementation 'com.android.support:multidex:1.0.3' +} diff --git a/frontend/android/app/src/main/AndroidManifest.xml b/frontend/android/app/src/main/AndroidManifest.xml index 6f2f4c8..949d141 100644 --- a/frontend/android/app/src/main/AndroidManifest.xml +++ b/frontend/android/app/src/main/AndroidManifest.xml @@ -44,4 +44,12 @@ + + + + + + + + diff --git a/frontend/android/build.gradle b/frontend/android/build.gradle index bc157bd..ceaf90e 100644 --- a/frontend/android/build.gradle +++ b/frontend/android/build.gradle @@ -1,3 +1,13 @@ +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:8.1.0' + } +} + allprojects { repositories { google() diff --git a/frontend/android/gradle.properties b/frontend/android/gradle.properties index 598d13f..06c53e1 100644 --- a/frontend/android/gradle.properties +++ b/frontend/android/gradle.properties @@ -1,3 +1,5 @@ org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true +android.useAndroidX=true +android.enableJetifier=true \ No newline at end of file diff --git a/frontend/android/gradle/wrapper/gradle-wrapper.properties b/frontend/android/gradle/wrapper/gradle-wrapper.properties index e1ca574..b115ac1 100644 --- a/frontend/android/gradle/wrapper/gradle-wrapper.properties +++ b/frontend/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip +distributionUrl=https://services.gradle.org/distributions/gradle-8.12.1-bin.zip diff --git a/frontend/android/settings.gradle b/frontend/android/settings.gradle index 1d6d19b..8a87a89 100644 --- a/frontend/android/settings.gradle +++ b/frontend/android/settings.gradle @@ -19,8 +19,8 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "7.3.0" apply false - id "org.jetbrains.kotlin.android" version "1.7.10" apply false + id "com.android.application" version "8.1.0" apply false + id "org.jetbrains.kotlin.android" version "2.1.10" apply false } include ":app" diff --git a/frontend/ios/Runner/Info.plist b/frontend/ios/Runner/Info.plist index 992d529..8cb0658 100644 --- a/frontend/ios/Runner/Info.plist +++ b/frontend/ios/Runner/Info.plist @@ -51,5 +51,9 @@ Camera Usage NSMicrophoneUsageDescription Microphone Usage + LSApplicationQueriesSchemes + + https + diff --git a/frontend/lib/To-Be-Discarded/bottomNav.dart b/frontend/lib/To-Be-Discarded/bottomNav.dart new file mode 100644 index 0000000..a31884f --- /dev/null +++ b/frontend/lib/To-Be-Discarded/bottomNav.dart @@ -0,0 +1,70 @@ +// import 'package:flutter/material.dart'; +// //Testing change +// import 'package:dev_track_app/pages/domain_pages/domain.dart'; +// import 'package:dev_track_app/pages/home.dart'; + +// class BottomNav extends StatefulWidget { +// const BottomNav({super.key}); + +// @override +// State createState() => _BottomNavState(); +// } + +// class _BottomNavState extends State { +// int _currentIndex = 0; +// final List pages = [ +// const HomePag(), +// // const ScrumPage(), +// const DomainPage(), +// // const ProgressPage(), +// // const ProfilePage(), +// ]; +// @override +// Widget build(BuildContext context) { +// return MaterialApp( +// home: Scaffold( +// body: pages[_currentIndex], +// bottomNavigationBar: BottomNavigationBar( +// currentIndex: _currentIndex, +// type: BottomNavigationBarType.fixed, +// backgroundColor: Colors.black, +// selectedItemColor: +// Colors.green, // Set the selected icon color to green +// unselectedItemColor: Color.fromARGB(255, 2, 155, 73), +// iconSize: 35, // Set the unselected icon color to green +// items: const [ +// BottomNavigationBarItem( +// icon: Icon(Icons.home), +// label: 'Feed', +// ), +// BottomNavigationBarItem( +// icon: Icon(Icons.supervisor_account), +// label: 'Scrum', +// ), +// BottomNavigationBarItem( +// icon: Icon(Icons.apps), +// label: 'Domain', +// ), +// BottomNavigationBarItem( +// icon: Icon(Icons.access_time), +// label: 'Progress', +// ), +// BottomNavigationBarItem( +// icon: Icon(Icons.person_rounded), +// label: 'Profile', +// ), +// ], +// onTap: (index) { +// setState(() { +// _currentIndex = index; +// }); +// }, +// ), +// ), +// ); +// } +// } + +// void main() { +// runApp(const BottomNav()); +// } diff --git a/frontend/lib/api/api.dart b/frontend/lib/api/api.dart new file mode 100644 index 0000000..e69de29 diff --git a/frontend/lib/api/login_api.dart b/frontend/lib/api/login_api.dart new file mode 100644 index 0000000..a718c68 --- /dev/null +++ b/frontend/lib/api/login_api.dart @@ -0,0 +1,32 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; + +import '../models/user_model.dart'; +// static const String baseUrl = "https://dev-track-app.onrender.com"; +// static const String loginEndpoint = "$baseUrl/user/login"; + +class AuthService { + final String baseUrl = "https://dev-track-app.onrender.com/api/user/login"; + + Future login(String email, String password) async { + try { + final response = await http.post( + Uri.parse(baseUrl), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode({"email": email, "password": password}), + ); + print("Response Status Code: ${response.statusCode}"); + print("Response Body: ${response.body}"); + + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + return UserModel.fromJson(data); + } else { + throw Exception('Failed to login ${response.body}'); + } + } catch (e) { + throw Exception('Error: $e'); + } + } +} diff --git a/frontend/lib/components/bottomNav.dart b/frontend/lib/components/bottomNav.dart deleted file mode 100644 index b38a23b..0000000 --- a/frontend/lib/components/bottomNav.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:dev_track_app/pages/domain.dart'; -import 'package:dev_track_app/pages/home.dart'; - -class BottomNav extends StatefulWidget { - const BottomNav({super.key}); - - @override - State createState() => _BottomNavState(); -} - -class _BottomNavState extends State { - int _currentIndex = 0; - final List pages = [ - const HomePag(), - // const ScrumPage(), - const DomainPage(), - // const ProgressPage(), - // const ProfilePage(), - ]; - @override - Widget build(BuildContext context) { - return MaterialApp( - home: Scaffold( - body: pages[_currentIndex], - bottomNavigationBar: BottomNavigationBar( - currentIndex: _currentIndex, - type: BottomNavigationBarType.fixed, - backgroundColor: Colors.black, - selectedItemColor: - Colors.green, // Set the selected icon color to green - unselectedItemColor: Color.fromARGB(255, 2, 155, 73), - iconSize: 35, // Set the unselected icon color to green - items: const [ - BottomNavigationBarItem( - icon: Icon(Icons.home), - label: 'Feed', - ), - BottomNavigationBarItem( - icon: Icon(Icons.supervisor_account), - label: 'Scrum', - ), - BottomNavigationBarItem( - icon: Icon(Icons.apps), - label: 'Domain', - ), - BottomNavigationBarItem( - icon: Icon(Icons.access_time), - label: 'Progress', - ), - BottomNavigationBarItem( - icon: Icon(Icons.person_rounded), - label: 'Profile', - ), - ], - onTap: (index) { - setState(() { - _currentIndex = index; - }); - }, - ), - ), - ); - } -} - -void main() { - runApp(const BottomNav()); -} diff --git a/frontend/lib/logic/logic.dart b/frontend/lib/logic/logic.dart new file mode 100644 index 0000000..e69de29 diff --git a/frontend/lib/logic/tracker_logic.dart b/frontend/lib/logic/tracker_logic.dart new file mode 100644 index 0000000..26ec9ca --- /dev/null +++ b/frontend/lib/logic/tracker_logic.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; + +class ProgressTrackerLogic { + /// Number of members to track + final int numberOfMembers; + + /// Which panels are expanded + late List isExpandedList; + + /// Text controllers for each member + late List progressControllers; + + ProgressTrackerLogic({this.numberOfMembers = 4}) { + isExpandedList = List.generate(numberOfMembers, (_) => false); + progressControllers = + List.generate(numberOfMembers, (_) => TextEditingController()); + } + + /// Toggle expansion for a specific member index + void toggleExpanded(int index) { + isExpandedList[index] = !isExpandedList[index]; + } + + /// Dispose of controllers to avoid memory leaks + void dispose() { + for (var controller in progressControllers) { + controller.dispose(); + } + } +} + diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index b8a282e..f95bc9f 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -1,9 +1,22 @@ +import 'package:dev_track_app/pages/common_pages/login_page.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; -import 'package:dev_track_app/pages/home.dart'; +import 'models/login_view_model.dart'; + +// void main() { +// runApp(const MyApp()); +// } void main() { - runApp(const MyApp()); + runApp( + MultiProvider( + providers: [ + ChangeNotifierProvider(create: (_) => LoginViewModel()), + ], + child: const MyApp(), + ), + ); } class MyApp extends StatelessWidget { @@ -17,7 +30,7 @@ class MyApp extends StatelessWidget { primarySwatch: Colors.green, ), debugShowCheckedModeBanner: false, - home: const HomePag(), + home: LoginPage(), ); } } diff --git a/frontend/lib/models/domain_model.dart b/frontend/lib/models/domain_model.dart new file mode 100644 index 0000000..37de0f3 --- /dev/null +++ b/frontend/lib/models/domain_model.dart @@ -0,0 +1,8 @@ +// final List> projects = List.generate( +// 6, +// (index) => { +// "name": "Project name", +// "team": "Team A", +// "description": "Lorem ipsum dolor sit amet.", +// }, +// ); diff --git a/frontend/lib/models/login_view_model.dart b/frontend/lib/models/login_view_model.dart new file mode 100644 index 0000000..efaccd1 --- /dev/null +++ b/frontend/lib/models/login_view_model.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../api/login_api.dart'; +import '../models/user_model.dart'; + +class LoginViewModel extends ChangeNotifier { + final AuthService _authService = AuthService(); + + bool isLoading = false; + String? errorMessage; + UserModel? user; + + Future login(String email, String password) async { + isLoading = true; + errorMessage = null; + notifyListeners(); + + try { + user = await _authService.login(email, password); + + final prefs = await SharedPreferences.getInstance(); + await prefs.setString('role', user!.role); + await prefs.setString('csrf_token', user!.csrfToken); + } catch (e) { + errorMessage = 'Login failed. Please check your credentials.'; + } + + isLoading = false; + notifyListeners(); + } + + Future getUserRole() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString('role'); + } +} diff --git a/frontend/lib/models/models.dart b/frontend/lib/models/models.dart new file mode 100644 index 0000000..e69de29 diff --git a/frontend/lib/models/new_specific_projectModels.dart b/frontend/lib/models/new_specific_projectModels.dart new file mode 100644 index 0000000..e3d1b1d --- /dev/null +++ b/frontend/lib/models/new_specific_projectModels.dart @@ -0,0 +1,95 @@ +// Team Member data model +class TeamMember { + final String name; + final String imageUrl; + final String linkedInUrl; + + TeamMember({ + required this.name, + required this.imageUrl, + required this.linkedInUrl, + }); + + // TODO: Add fromJson constructor for API integration + // factory TeamMember.fromJson(Map json) { + // return TeamMember( + // name: json['name'], + // imageUrl: json['image_url'], + // linkedInUrl: json['linkedin_url'], + // ); + // } +} + +// Data model for specific project details +class SpecificProjectData { + final String projectName; + final String projectCycle; + final String projectImage; + final String description; + final double progress; + final String githubLink; + final String projectLink; + final List teamMembers; + + SpecificProjectData({ + required this.projectName, + required this.projectCycle, + required this.projectImage, + required this.description, + required this.progress, + required this.githubLink, + required this.projectLink, + required this.teamMembers, + }); + + // TODO: Add fromJson constructor for API integration + // factory SpecificProjectData.fromJson(Map json) { + // return SpecificProjectData( + // projectName: json['project_name'], + // projectCycle: json['project_cycle'], + // projectImage: json['project_image'], + // description: json['description'], + // progress: json['progress'].toDouble(), + // githubLink: json['github_link'], + // projectLink: json['project_link'], + // teamMembers: (json['team_members'] as List) + // .map((member) => TeamMember.fromJson(member)) + // .toList(), + // ); + // } +} + +// Dummy data for development +final dummyProjectData = SpecificProjectData( + projectName: "SAMPLE PROJECT", + projectCycle: "Project Cycle 1", + projectImage: "https://i.imgur.com/ea9PB3H.png", + description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Pellentesque at eros finibus, scelerisque elit at, iaculis " + "massa. Phasellus a risus dolor.", + progress: 0.69, + githubLink: "https://github.com/sample/project", + projectLink: "https://project-link.com", + teamMembers: [ + TeamMember( + name: "Piyush Chakarborthy", + imageUrl: "https://i.imgur.com/GKPso4W.jpeg", + linkedInUrl: "https://www.linkedin.com/in/piyushc2003/", + ), + TeamMember( + name: "Jane Smith", + imageUrl: "https://via.placeholder.com/150", + linkedInUrl: "https://www.linkedin.com/in/janesmith", + ), + TeamMember( + name: "Mike Johnson", + imageUrl: "https://via.placeholder.com/150", + linkedInUrl: "https://www.linkedin.com/in/mikejohnson", + ), + TeamMember( + name: "Sarah Wilson", + imageUrl: "https://via.placeholder.com/150", + linkedInUrl: "https://www.linkedin.com/in/sarahwilson", + ), + ], +); diff --git a/frontend/lib/models/previous_projects_models.dart b/frontend/lib/models/previous_projects_models.dart new file mode 100644 index 0000000..c84ccc4 --- /dev/null +++ b/frontend/lib/models/previous_projects_models.dart @@ -0,0 +1,33 @@ + // final List> projects = + // [ + // { + // "title": "UI/UX 42", + // "subtitle": "Budgeting application", + // "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + // "image": "assets/images/game.png" + // }, + // { + // "title": "Dev Track", + // "subtitle": "Project management tool", + // "description": "A tool to track developer progress in real-time.", + // "image": "assets/images/devtrack.png" + // }, + // { + // "title": "E-commerce App", + // "subtitle": "Shopping made easy", + // "description": "An intuitive mobile shopping experience.", + // "image": "assets/images/ecommerce.png" + // }, + // { + // "title": "E-commerce App", + // "subtitle": "Shopping made easy", + // "description": "An intuitive mobile shopping experience.", + // "image": "assets/images/ecommerce.png" + // }, + // { + // "title": "E-commerce App", + // "subtitle": "Shopping made easy", + // "description": "An intuitive mobile shopping experience.", + // "image": "assets/images/ecommerce.png" + // }, + // ]; \ No newline at end of file diff --git a/frontend/lib/pages/std_class.dart b/frontend/lib/models/std_class.dart similarity index 96% rename from frontend/lib/pages/std_class.dart rename to frontend/lib/models/std_class.dart index 4505c18..44b4ae3 100644 --- a/frontend/lib/pages/std_class.dart +++ b/frontend/lib/models/std_class.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -class STD{ +class STD { final String _id; final String _name; @@ -11,9 +11,6 @@ class STD{ final String _position; - - - STD(this._id, this._name, this._profilepic, this._linkedin, this._position); String get id => _id; @@ -25,6 +22,4 @@ class STD{ String get linkedin => _linkedin; String get profilepic => _profilepic; - - -} \ No newline at end of file +} diff --git a/frontend/lib/models/std_details.dart b/frontend/lib/models/std_details.dart new file mode 100644 index 0000000..4546c35 --- /dev/null +++ b/frontend/lib/models/std_details.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; +import 'std_class.dart'; + +class STD_details { + static List stdlist = [ + STD("070", "Vaishali", "assets/images/elmoo.jpg", + "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "Lead"), + STD("071", "Areny", "assets/images/elmoo.jpg", + "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "Front-End"), + STD("072", "Atul", "assets/images/elmoo.jpg", + "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "Back-End"), + STD("073", "Vais", "assets/images/elmoo.jpg", + "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "UI/UX"), + ]; +} diff --git a/frontend/lib/models/submission_model.dart b/frontend/lib/models/submission_model.dart new file mode 100644 index 0000000..fae9474 --- /dev/null +++ b/frontend/lib/models/submission_model.dart @@ -0,0 +1,6 @@ +const List submissionList = [ + 'AppDev-51', + 'AppDev-52', + 'WebDev-41', + 'WebDev-42' +]; diff --git a/frontend/lib/models/user_model.dart b/frontend/lib/models/user_model.dart new file mode 100644 index 0000000..60be2b7 --- /dev/null +++ b/frontend/lib/models/user_model.dart @@ -0,0 +1,16 @@ +class UserModel { + final String message; + final String role; + final String csrfToken; + + UserModel( + {required this.message, required this.role, required this.csrfToken}); + + factory UserModel.fromJson(Map json) { + return UserModel( + message: json['message'], + role: json['role'], + csrfToken: json['csrf_token'], + ); + } +} diff --git a/frontend/lib/pages/admin_pages/admin_dummy_home.dart b/frontend/lib/pages/admin_pages/admin_dummy_home.dart new file mode 100644 index 0000000..31c71f8 --- /dev/null +++ b/frontend/lib/pages/admin_pages/admin_dummy_home.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +class AdminDummyHome extends StatefulWidget { + const AdminDummyHome({super.key}); + + @override + State createState() => _AdminDummyHomeState(); +} + +class _AdminDummyHomeState extends State { + @override + Widget build(BuildContext context) { + return const Scaffold( + body: Center( + child: Text('admin home'), + ), + ); + } +} diff --git a/frontend/lib/pages/admin_pages/admin_feed_page.dart b/frontend/lib/pages/admin_pages/admin_feed_page.dart new file mode 100644 index 0000000..4453a42 --- /dev/null +++ b/frontend/lib/pages/admin_pages/admin_feed_page.dart @@ -0,0 +1,284 @@ +import 'package:flutter/material.dart'; + + +class AdminFeedPage extends StatefulWidget { + const AdminFeedPage({super.key}); + + @override + State createState() => _FeedScreenState(); +} + +class _FeedScreenState extends State { + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + backgroundColor: Colors.white, + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildTopBar(), + const SizedBox(height: 10), + _buildHeader(), + const SizedBox(height: 10), + _buildTabBar(), + const SizedBox(height: 10), + Expanded( + child: ListView.builder( + itemCount: 3, + itemBuilder: (context, index) => _buildPostCard(), + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildTopBar() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.black), + onPressed: () {}, + ), + IconButton( + icon: const Icon(Icons.notifications, color: Colors.black), + onPressed: () {}, + ), + ], + ); + } + + Widget _buildHeader() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + "Feed", + style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.purple, + foregroundColor: Colors.white, + ), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const CreatePostPage()), + ); + }, + child: const Text("+ Create Post"), + ), + ], + ); + } + + Widget _buildTabBar() { + return Container( + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildTab("Primary", isSelected: true), + _buildTab("Secondary"), + _buildTab("Ternary"), + ], + ), + ); + } + + Widget _buildTab(String title, {bool isSelected = false}) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + child: Text( + title, + style: TextStyle( + fontSize: 16, + fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, + color: isSelected ? Colors.purple : Colors.black, + ), + ), + ); + } + + Widget _buildPostCard() { + return Container( + margin: const EdgeInsets.symmetric(vertical: 8), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const CircleAvatar( + backgroundColor: Colors.grey, + radius: 20, + ), + const SizedBox(width: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + Text( + "Name", + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), + ), + Text( + "2 Days ago • DD/month Time", + style: TextStyle(fontSize: 12, color: Colors.grey), + ), + ], + ), + ], + ), + const SizedBox(height: 10), + const Text( + "Lorem ipsum dolor sit amet et delectus accommodare " + "his consul copiosae legendos at vix ad putent delectus " + "delicata usu.", + style: TextStyle(fontSize: 14), + ), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: List.generate( + 3, + (index) => Container( + margin: const EdgeInsets.only(right: 8), + height: 10, + width: 30, + color: Colors.grey[300], + ), + ), + ), + FloatingActionButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => EditPostPage()), + ); + }, + backgroundColor: Colors.purple, + mini: true, + child: const Icon(Icons.edit, color: Colors.white, size: 18), + ), + ], + ), + ], + ), + ); + } +} + +class CreatePostPage extends StatelessWidget { + const CreatePostPage({Key? key}) : super(key: key); + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Center( + child: Container( + width: MediaQuery.of(context).size.width * 0.8, + height: MediaQuery.of(context).size.height * 0.5, + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Colors.purple, width: 2), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Create Post', + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: TextField( + maxLines: 4, + decoration: InputDecoration( + border: OutlineInputBorder(), + hintText: 'Type your message', + ), + ), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.purple), + onPressed: () { + }, + child: Text('Post', + style: TextStyle(color: Colors.white), + ), + ), + ], + ), + ), + ), + ), + ); + } +} + +class EditPostPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Center( + child: Container( + width: MediaQuery.of(context).size.width * 0.8, + height: MediaQuery.of(context).size.height * 0.5, + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Colors.purple, width: 2), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Edit Post', + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: TextField( + maxLines: 4, + decoration: InputDecoration( + border: OutlineInputBorder(), + hintText: 'Type your message', + ), + ), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.purple), + onPressed: () { + }, + child: Text('Save', + style: TextStyle(color: Colors.white), + ), + ), + ], + ), + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/frontend/lib/pages/mgmt_previous_projects/mgmg_prev_projects.dart b/frontend/lib/pages/admin_pages/mgmg_prev_projects.dart similarity index 100% rename from frontend/lib/pages/mgmt_previous_projects/mgmg_prev_projects.dart rename to frontend/lib/pages/admin_pages/mgmg_prev_projects.dart diff --git a/frontend/lib/pages/mgmt_previous_projects/projects_card.dart b/frontend/lib/pages/admin_pages/projects_card.dart similarity index 80% rename from frontend/lib/pages/mgmt_previous_projects/projects_card.dart rename to frontend/lib/pages/admin_pages/projects_card.dart index 7aa9244..42949c0 100644 --- a/frontend/lib/pages/mgmt_previous_projects/projects_card.dart +++ b/frontend/lib/pages/admin_pages/projects_card.dart @@ -1,25 +1,20 @@ import 'package:flutter/material.dart'; - -import '../specific_project.dart'; - +import 'package:dev_track_app/pages/user_pages/project_pages/project_display/specific_project.dart'; class Data { String domain_name; String content; - Data( - {required this.domain_name, - - required this.content}); + Data({required this.domain_name, required this.content}); } Widget ProjectsCard(List projectlist, int index, BuildContext contextt, Function onUpdate, Function onDelete) { TextEditingController _domainController = - TextEditingController(text: projectlist[index].domain_name); + TextEditingController(text: projectlist[index].domain_name); TextEditingController _contentController = - TextEditingController(text: projectlist[index].content); + TextEditingController(text: projectlist[index].content); return Padding( padding: const EdgeInsets.fromLTRB(0, 15, 0, 15), @@ -59,9 +54,12 @@ Widget ProjectsCard(List projectlist, int index, BuildContext contextt, ), Text( projectlist[index].domain_name, - style: TextStyle(fontSize: 15,color: Colors.white), + style: TextStyle(fontSize: 15, color: Colors.white), + ), + Text( + projectlist[index].content, + style: TextStyle(color: Colors.white), ), - Text(projectlist[index].content,style: TextStyle(color: Colors.white),), ], ), ], @@ -72,12 +70,12 @@ Widget ProjectsCard(List projectlist, int index, BuildContext contextt, child: Container( height: 50, width: 50, - child: Container( width: 30, child: PopupMenuButton( offset: Offset(0, 40), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10)), itemBuilder: (context) => [ PopupMenuItem( child: Row( @@ -108,16 +106,19 @@ Widget ProjectsCard(List projectlist, int index, BuildContext contextt, TextField( controller: _domainController, cursorColor: Colors.black45, - style: TextStyle(color: Colors.black), + style: TextStyle( + color: Colors.black), decoration: InputDecoration( labelText: 'Edit Caption', labelStyle: TextStyle( color: Colors.black45), - enabledBorder: UnderlineInputBorder( + enabledBorder: + UnderlineInputBorder( borderSide: BorderSide( color: Colors.black38), ), - focusedBorder: UnderlineInputBorder( + focusedBorder: + UnderlineInputBorder( borderSide: BorderSide( color: Colors.black45), ), @@ -126,16 +127,19 @@ Widget ProjectsCard(List projectlist, int index, BuildContext contextt, TextField( controller: _contentController, cursorColor: Colors.black45, - style: TextStyle(color: Colors.black), + style: TextStyle( + color: Colors.black), decoration: InputDecoration( labelText: 'Edit Content', labelStyle: TextStyle( color: Colors.black45), - enabledBorder: UnderlineInputBorder( + enabledBorder: + UnderlineInputBorder( borderSide: BorderSide( color: Colors.black38), ), - focusedBorder: UnderlineInputBorder( + focusedBorder: + UnderlineInputBorder( borderSide: BorderSide( color: Colors.black45), ), @@ -153,11 +157,12 @@ Widget ProjectsCard(List projectlist, int index, BuildContext contextt, }, child: Text( 'Update', - style: - TextStyle(color: Colors.white), + style: TextStyle( + color: Colors.white), ), style: ElevatedButton.styleFrom( - backgroundColor: Color(0xFF93B1A6), + backgroundColor: + Color(0xFF93B1A6), ), ), ], @@ -188,11 +193,7 @@ Widget ProjectsCard(List projectlist, int index, BuildContext contextt, ], iconColor: Color(0xFF93B1A6), ), - - ), - - ), ), ], @@ -208,15 +209,18 @@ Widget ProjectsCard(List projectlist, int index, BuildContext contextt, height: double.infinity, child: ElevatedButton( onPressed: () { - Navigator.push(contextt, - MaterialPageRoute(builder: (context) => SpecificProject())); + Navigator.push( + contextt, + MaterialPageRoute( + builder: (context) => ProjectDetailPage())); }, child: Text( 'Learn More', style: TextStyle(fontSize: 15, color: Colors.grey[800]), ), style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.zero), + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.zero), backgroundColor: Colors.grey[400], ), ), diff --git a/frontend/lib/pages/common_pages/Theme-Demo-Page/sample.dart b/frontend/lib/pages/common_pages/Theme-Demo-Page/sample.dart new file mode 100644 index 0000000..1f85d9b --- /dev/null +++ b/frontend/lib/pages/common_pages/Theme-Demo-Page/sample.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:dev_track_app/theme/theme.dart'; // Import your theme file + +class ThemedPage extends StatelessWidget { + const ThemedPage({super.key}); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final colorScheme = theme.colorScheme; + final textTheme = theme.textTheme; + + return Scaffold( + appBar: AppBar( + title: Text('Themed Page', style: textTheme.headlineLarge), + backgroundColor: colorScheme.primary, + ), + body: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Text Styles + Text('Display Large', style: textTheme.displayLarge), + Text('Display Medium', style: textTheme.displayMedium), + Text('Display Small', style: textTheme.displaySmall), + Text('Headline Large', style: textTheme.headlineLarge), + Text('Title Large', style: textTheme.titleLarge), + Text('Body Large (Secondary Text)', style: textTheme.bodyLarge), + const SizedBox(height: 20), + + // Themed Buttons + ElevatedButton( + onPressed: () {}, + style: ElevatedButton.styleFrom( + backgroundColor: colorScheme.primary, + ), + child: Text('Primary Button', style: textTheme.titleLarge), + ), + const SizedBox(height: 10), + ElevatedButton( + onPressed: () {}, + style: ElevatedButton.styleFrom( + backgroundColor: colorScheme.secondary, + ), + child: Text('Secondary Button', style: textTheme.titleLarge), + ), + const SizedBox(height: 10), + OutlinedButton( + onPressed: () {}, + style: OutlinedButton.styleFrom( + foregroundColor: colorScheme.onBackground, + ), + child: Text('Outlined Button', style: textTheme.titleLarge), + ), + const SizedBox(height: 20), + + // Colored Containers + Container( + height: 50, + width: double.infinity, + color: colorScheme.surface, + child: Center( + child: Text('Surface Color', style: textTheme.bodyLarge), + ), + ), + const SizedBox(height: 10), + Container( + height: 50, + width: double.infinity, + color: colorScheme.background, + child: Center( + child: Text('Background Color', style: textTheme.bodyLarge), + ), + ), + const SizedBox(height: 20), + + // Accent Color + Container( + height: 50, + width: double.infinity, + color: Theme.of(context).brightness == Brightness.light + ? const Color(0xFFFFC107) // Light mode accent color + : const Color(0xFF6870fa), // Dark mode accent color + child: Center( + child: Text('Accent Color', style: textTheme.bodyLarge), + ), + ), + ], + ), + ), + ); + } +} diff --git a/frontend/lib/pages/confirm_page.dart b/frontend/lib/pages/common_pages/confirm_page.dart similarity index 100% rename from frontend/lib/pages/confirm_page.dart rename to frontend/lib/pages/common_pages/confirm_page.dart diff --git a/frontend/lib/pages/common_pages/domain_pages/domain.dart b/frontend/lib/pages/common_pages/domain_pages/domain.dart new file mode 100644 index 0000000..3341309 --- /dev/null +++ b/frontend/lib/pages/common_pages/domain_pages/domain.dart @@ -0,0 +1,159 @@ +import 'package:flutter/material.dart'; + +class DomainPage extends StatefulWidget { + const DomainPage({super.key}); + + @override + State createState() => _DomainPageState(); +} + +class _DomainPageState extends State { + final List> projects = List.generate( + 6, + (index) => { + "name": "Project name", + "team": "Team A", + "description": "Lorem ipsum dolor sit amet.", + }, + ); + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + appBar: appBarCommon(), + backgroundColor: Colors.white, + body: Column( + children: [ + searchBar(), + welcomeBack(), + const SizedBox(height: 20), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: ListView.builder( + itemCount: 3, + itemBuilder: (context, index) => domainArea(), + ), + ), + ), + ], + ), + ), + ); + } + + PreferredSizeWidget appBarCommon() { + return AppBar( + elevation: 0.00, + backgroundColor: Colors.white, + actions: [ + IconButton( + icon: const Icon(Icons.notifications), + tooltip: 'Setting Icon', + onPressed: () {}, + ), //IconButton + ], + ); + } + + Widget searchBar() { + return Container( + padding: EdgeInsets.all(16), + child: TextField( + decoration: InputDecoration( + labelText: 'search all projects', + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(20.0)), + ), + prefixIcon: Icon(Icons.search), + ), + ), + ); + } + + Widget welcomeBack() { + return Container( + padding: const EdgeInsets.all(10), + margin: const EdgeInsets.symmetric(horizontal: 14), + child: Column( + children: [ + Row( + children: [ + Text( + 'Welcome Back,', + style: TextStyle(fontSize: 30), + ), + ], + ), + Row( + children: [ + Text( + 'STUDENT', + style: TextStyle(fontSize: 40), + ), + ], + ) + ], + ), + ); + } + + Widget domainArea() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("AI/ML", + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + SizedBox(height: 8), + SizedBox( + height: 130, + //width: 80, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: projects.map((project) { + return Padding( + padding: const EdgeInsets.only(right: 10), + child: SizedBox(width: 170, child: domainCards(project)), + ); + }).toList(), + ), + ), + ), + SizedBox(height: 10), + Text( + "Explore more projects", + style: TextStyle( + color: Colors.purple, + decoration: TextDecoration.underline, + decorationThickness: 2), + ), + SizedBox(height: 20), + ], + ); + } + + Widget domainCards(Map project) { + return Card( + shadowColor: Colors.black87, + elevation: 5, + color: Color.fromRGBO(122, 36, 180, 1), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(project["name"]!, + style: TextStyle( + color: Colors.white, fontWeight: FontWeight.bold)), + Text(project["team"]!, style: TextStyle(color: Colors.white70)), + SizedBox(height: 5), + Text(project["description"]!, + style: TextStyle(color: Colors.white70, fontSize: 12)), + ], + ), + ), + ); + } +} diff --git a/frontend/lib/components/topNav.dart b/frontend/lib/pages/common_pages/domain_pages/topNav.dart similarity index 100% rename from frontend/lib/components/topNav.dart rename to frontend/lib/pages/common_pages/domain_pages/topNav.dart diff --git a/frontend/lib/pages/common_pages/general_feed_page.dart b/frontend/lib/pages/common_pages/general_feed_page.dart new file mode 100644 index 0000000..2be75c3 --- /dev/null +++ b/frontend/lib/pages/common_pages/general_feed_page.dart @@ -0,0 +1,237 @@ +import 'package:dev_track_app/pages/common_pages/login_page.dart'; +import 'package:flutter/material.dart'; + + + +class GeneralFeedPage extends StatefulWidget { + const GeneralFeedPage({super.key}); + + @override + State createState() => _GeneralFeedPageState(); +} + +class Post { + final String details; + + Post({required this.details}); +} + + + +class _GeneralFeedPageState extends State { + final List posts = [ + Post(details: "Post 1 details"), + Post(details: 'Post 2 details'), + Post(details: 'Post 3 details'), + ]; + + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + backgroundColor: Colors.white, + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildTopBar(), + const SizedBox(height: 10), + _buildHeader(), + const SizedBox(height: 10), + _buildTabBar(), + const SizedBox(height: 10), + Expanded( + child: ListView.builder( + itemCount: posts.length, + itemBuilder: (context, index) => PostCard( + post: posts[index], + onViewMore: () => showPopup(context, posts[index]), + ), + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildTopBar() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.black), + onPressed: () { + Navigator.pop(context); }, + ), + Padding( + padding: const EdgeInsets.fromLTRB(25, 15, 25, 10), + child: ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => LoginPage()), + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.purple, + side: BorderSide(color: Color.fromARGB(255, 253, 253, 253)), + padding: EdgeInsets.symmetric(vertical: 10), + textStyle: TextStyle(fontSize: 18, color: Colors.grey), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: Center( + child: Text('login', + style: TextStyle(color: Colors.white)), + ), + ), + ), + ], + ); + } + + void showPopup(BuildContext context, Post post) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text("Post Details"), + content: Text(post.details), // fetchhh post details..... + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text("Close"), + ), + ], + ); + }, + ); + } + + Widget _buildHeader() { + return const Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("WELCOME BACK"), + Text( + "Bharathan", + style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold), + ), + ], + ); + } + + Widget _buildTabBar() { + return Container( + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildTab("Primary", isSelected: true), + _buildTab("Secondary"), + _buildTab("Ternary"), + ], + ), + ); + } + + Widget _buildTab(String title, {bool isSelected = false}) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + child: Text( + title, + style: TextStyle( + fontSize: 16, + fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, + color: isSelected ? Colors.purple : Colors.black, + ), + ), + ); + } +} + + + +class PostCard extends StatelessWidget { + final Post post; + final VoidCallback onViewMore; + + const PostCard({super.key, required this.post, required this.onViewMore}); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 8), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const CircleAvatar( + backgroundColor: Colors.grey, + radius: 20, + ), + const SizedBox(width: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + Text( + "Name", + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), + ), + Text( + "2 Days ago • DD/month Time", + style: TextStyle(fontSize: 12, color: Colors.grey), + ), + ], + ), + ], + ), + const SizedBox(height: 10), + Text( + post.details, + style: const TextStyle(fontSize: 14), + ), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: List.generate( + 3, + (index) => Container( + margin: const EdgeInsets.only(right: 8), + height: 10, + width: 30, + color: Colors.grey[300], + ), + ), + ), + FloatingActionButton( + tooltip: 'View More', + onPressed: onViewMore, + backgroundColor: Colors.purple, + mini: true, + child: const Icon(Icons.arrow_forward, color: Colors.white, size: 18), + ), + ], + ), + ], + ), + ); + } +} diff --git a/frontend/lib/pages/home_page.dart b/frontend/lib/pages/common_pages/home_page.dart similarity index 53% rename from frontend/lib/pages/home_page.dart rename to frontend/lib/pages/common_pages/home_page.dart index e36f50e..929cad3 100644 --- a/frontend/lib/pages/home_page.dart +++ b/frontend/lib/pages/common_pages/home_page.dart @@ -1,6 +1,8 @@ +import 'package:dev_track_app/pages/common_pages/login_page.dart'; +import 'package:dev_track_app/pages/common_pages/register_page.dart'; +import 'package:dev_track_app/utils/bottom_nav_bar.dart'; +import 'package:dev_track_app/utils/custom_button.dart'; import 'package:flutter/material.dart'; -import 'login_page.dart'; -import 'register_page.dart'; class HomePage extends StatelessWidget { const HomePage({super.key}); @@ -44,47 +46,27 @@ class HomePage extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - ElevatedButton( + CustomButton( + text: 'Login', onPressed: () { Navigator.push( context, MaterialPageRoute( - builder: (context) => const LoginPage()), + builder: (context) => LoginPage(), + ), ); }, - style: ElevatedButton.styleFrom( - backgroundColor: - const Color.fromARGB(255, 53, 156, 19), - fixedSize: const Size(120, 50), - ), - child: const Text( - 'Login', - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - ), - ), ), - ElevatedButton( + CustomButton( + text: 'Register', onPressed: () { Navigator.push( context, MaterialPageRoute( - builder: (context) => const RegisterPage()), + builder: (context) => const RegisterPage(), + ), ); }, - style: ElevatedButton.styleFrom( - backgroundColor: - const Color.fromARGB(255, 53, 156, 19), - fixedSize: const Size(120, 50), - ), - child: const Text( - 'Register', - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - ), - ), ), ], ), @@ -94,25 +76,7 @@ class HomePage extends StatelessWidget { ], ), ), - bottomNavigationBar: BottomNavigationBar( - backgroundColor: Colors.grey[850], - selectedItemColor: Colors.white, - unselectedItemColor: Colors.grey, - items: const [ - BottomNavigationBarItem( - icon: Icon(Icons.home), - label: 'Home', - ), - BottomNavigationBarItem( - icon: Icon(Icons.person), - label: 'Profile', - ), - BottomNavigationBarItem( - icon: Icon(Icons.settings), - label: 'Settings', - ), - ], - ), + bottomNavigationBar: const BottomNavBar(), ); } } diff --git a/frontend/lib/pages/common_pages/login_page.dart b/frontend/lib/pages/common_pages/login_page.dart new file mode 100644 index 0000000..6564d65 --- /dev/null +++ b/frontend/lib/pages/common_pages/login_page.dart @@ -0,0 +1,189 @@ +import 'package:dev_track_app/pages/admin_pages/admin_dummy_home.dart'; +import 'package:dev_track_app/pages/common_pages/general_feed_page.dart'; +import 'package:dev_track_app/pages/common_pages/register_page.dart'; +import 'package:dev_track_app/pages/user_pages/user_dummy_home.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../../models/login_view_model.dart'; + +class LoginPage extends StatelessWidget { + final TextEditingController emailController = TextEditingController(); + final TextEditingController passwordController = TextEditingController(); + + LoginPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final loginViewModel = Provider.of(context); + + return Scaffold( + backgroundColor: Colors.white, + body: SingleChildScrollView( + child: Center( + child: Column( + children: [ + Stack( + alignment: Alignment.center, + children: [ + Container( + height: 150, + decoration: const BoxDecoration( + color: Color(0xFF5e00b0), + shape: BoxShape.rectangle, + borderRadius: + BorderRadius.vertical(top: Radius.circular(10)), + ), + ), + const Positioned( + top: 48, + child: CircleAvatar( + radius: 50, + backgroundColor: Colors.white, + child: CircleAvatar( + radius: 45, + backgroundImage: NetworkImage( + 'https://storage.googleapis.com/a1aa/image/FMzESL12uGqBDRIcbgyzHWSJA_eagcTLOcV2KYexVXY.jpg', + ), + ), + ), + ), + ], + ), + const SizedBox(height: 60), + const Text( + 'Welcome Back', + style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 10), + const Text( + 'Sign in to your account', + style: TextStyle(fontSize: 16, color: Colors.black54), + ), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 25), + child: TextField( + controller: emailController, + decoration: const InputDecoration( + prefixIcon: Icon(Icons.person), + labelText: 'Email', + ), + ), + ), + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 25, vertical: 10), + child: TextField( + controller: passwordController, + obscureText: true, + decoration: const InputDecoration( + prefixIcon: Icon(Icons.lock), + labelText: 'Password', + suffix: Text( + 'Forgot ?', + style: TextStyle(color: Colors.black54), + ), + ), + ), + ), + if (loginViewModel.errorMessage != null) + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Text( + loginViewModel.errorMessage!, + style: const TextStyle(color: Colors.red), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 25), + child: ElevatedButton( + onPressed: loginViewModel.isLoading + ? null + : () async { + await loginViewModel.login( + emailController.text, + passwordController.text, + ); + + if (loginViewModel.user != null) { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => + loginViewModel.user!.role == 'admin' + ? AdminDummyHome() + : UserDummyHome(), + ), + ); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF5e00b0), + padding: const EdgeInsets.symmetric(vertical: 10), + ), + child: loginViewModel.isLoading + ? const CircularProgressIndicator(color: Colors.white) + : const Text('Login', + style: TextStyle(color: Colors.white)), + ), + ), + const SizedBox(height: 15), + Text.rich( + TextSpan( + text: "Don't have an account? ", + children: [ + TextSpan( + text: 'Create', + style: const TextStyle( + color: Color(0xFF5e00b0), + decoration: TextDecoration.underline, + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const RegisterPage()), + ); + }, + ), + ], + ), + ), + const SizedBox(height: 20), + const Text('OR', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + Padding( + padding: const EdgeInsets.fromLTRB(25, 15, 25, 10), + child: ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const GeneralFeedPage()), + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + side: BorderSide(color: Color(0xFF5e00b0)), + padding: EdgeInsets.symmetric(vertical: 10), + textStyle: TextStyle(fontSize: 18, color: Colors.grey), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: Center( + child: Text('Browse as a Guest', + style: TextStyle(color: Colors.black)), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/frontend/lib/pages/register_page.dart b/frontend/lib/pages/common_pages/register_page.dart similarity index 100% rename from frontend/lib/pages/register_page.dart rename to frontend/lib/pages/common_pages/register_page.dart diff --git a/frontend/lib/pages/domain.dart b/frontend/lib/pages/domain.dart deleted file mode 100644 index a6068ab..0000000 --- a/frontend/lib/pages/domain.dart +++ /dev/null @@ -1,175 +0,0 @@ -import 'package:dev_track_app/components/topNav.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/painting.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/widgets.dart'; -import 'package:google_fonts/google_fonts.dart'; - -class DomainPage extends StatelessWidget { - const DomainPage({super.key}); - - @override - Widget build(BuildContext context) { - return SafeArea( - child: Scaffold( - backgroundColor: Color(0xFF040D12), - body: Column( - children: [ - TopNav(), - Container( - padding: EdgeInsets.all(10), - margin: EdgeInsets.only(left: 14, right: 14), - decoration: BoxDecoration( - color: Color(0xFF183D3D), - borderRadius: BorderRadius.circular(23), - ), - child: Column( - children: [ - Row( - children: [ - Container( - height: 70, - width: 70, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(35), - color: Color(0xFF5C8374)), - child: Align( - alignment: Alignment.center, - child: Icon( - Icons.pedal_bike, - color: Colors.white, - size: 38, - ), - ), - ), - SizedBox( - width: 4, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "My Cycle", - style: GoogleFonts.inter( - textStyle: TextStyle( - color: Colors.white, - fontSize: 19, - fontWeight: FontWeight.w500), - ), - ), - Text( - "Web Dev 68", - style: GoogleFonts.inter( - textStyle: TextStyle( - color: Color(0xFFC6C6C6), - fontSize: 14, - fontWeight: FontWeight.w500), - ), - ) - ], - ) - ], - ), - SizedBox( - height: 20, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Text( - 'Week', - style: GoogleFonts.inter( - textStyle: TextStyle( - fontSize: 21, - color: Color(0xFF999999), - ), - ), - ), - SizedBox( - width: 2, - ), - Text( - '4', - style: GoogleFonts.inter( - textStyle: TextStyle( - fontSize: 21, - color: Colors.white, - ), - ), - ), - ], - ), - ElevatedButton( - style: ButtonStyle( - padding: MaterialStatePropertyAll(EdgeInsets.only( - top: 20, bottom: 20, left: 45, right: 45)), - backgroundColor: - MaterialStatePropertyAll(Color(0xFF93B1A6)), - foregroundColor: - MaterialStatePropertyAll(Colors.white), - textStyle: MaterialStatePropertyAll(TextStyle( - fontSize: 19, fontWeight: FontWeight.bold))), - child: Text("Review"), - onPressed: null, - ) - ], - ), - ], - ), - ), - SizedBox( - height: 40, - ), - Expanded( - child: GridView.count( - crossAxisCount: 2, - padding: EdgeInsets.only(left: 14, right: 14), - mainAxisSpacing: 7, - crossAxisSpacing: 7, - children: - List.generate( - 6, - (index) => Container( - decoration: BoxDecoration( - color: Color(0xFF183D3D), - borderRadius: BorderRadius.circular(24)), - child: Align( - alignment: Alignment.center, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Image.asset( - 'assets/images/game.png', - fit: BoxFit.cover, - height: 58, - width: 111, - ), - SizedBox( - height: 28, - ), - Text( - "Game Dev", - style: GoogleFonts.hiMelody( - textStyle: TextStyle( - fontSize: 29, - color: Colors.white, - )), - ) - ], - ), - ), - ), - ) - - ), - ) - ], - ), - ), - ); - } -} diff --git a/frontend/lib/pages/home.dart b/frontend/lib/pages/home.dart index b8d30e4..41cb8d1 100644 --- a/frontend/lib/pages/home.dart +++ b/frontend/lib/pages/home.dart @@ -1,304 +1,62 @@ -import 'package:dev_track_app/components/bottomNav.dart'; -import 'package:dev_track_app/pages/mgmt_previous_projects/mgmg_prev_projects.dart'; -import 'package:dev_track_app/pages/previous_projects.dart'; -import 'package:dev_track_app/pages/splashscreen.dart'; -import 'package:dev_track_app/pages/confirm_page.dart'; -import 'package:dev_track_app/pages/domain.dart'; -import 'package:dev_track_app/pages/specific_project.dart'; -import 'package:dev_track_app/pages/home_page.dart'; +import 'package:dev_track_app/pages/admin_pages/admin_feed_page.dart'; +import 'package:dev_track_app/pages/user_pages/user_feed_page.dart'; import 'package:flutter/material.dart'; -import 'package:dev_track_app/pages/tracker.dart'; -import 'package:dev_track_app/pages/studentview.dart'; -import 'package:dev_track_app/pages/submission_page.dart'; - +import 'package:dev_track_app/pages/common_pages/Theme-Demo-Page/sample.dart'; +import 'package:dev_track_app/pages/admin_pages/mgmg_prev_projects.dart'; +import 'package:dev_track_app/pages/user_pages/project_pages/project_display/previous_projects.dart'; +import 'package:dev_track_app/theme/splashscreen.dart'; +import 'package:dev_track_app/pages/common_pages/confirm_page.dart'; +import 'package:dev_track_app/pages/common_pages/domain_pages/domain.dart'; +import 'package:dev_track_app/pages/user_pages/project_pages/project_display/specific_project.dart'; +import 'package:dev_track_app/pages/common_pages/home_page.dart'; +import 'package:dev_track_app/pages/user_pages/tracker.dart'; +import 'package:dev_track_app/pages/user_pages/studentview.dart'; +import 'package:dev_track_app/pages/user_pages/project_pages/submission_page/submission_page.dart'; class HomePag extends StatelessWidget { const HomePag({super.key}); + Widget buildNavButton(BuildContext context, String text, Color color, Widget page) { + return ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: color, + foregroundColor: Colors.white, + shadowColor: Colors.blueAccent, + elevation: 5, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(30.0), + ), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), + textStyle: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + onPressed: () { + Navigator.push(context, MaterialPageRoute(builder: (context) => page)); + }, + child: Text(text), + ); + } + @override - Widget build(BuildContext context) { + Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.center, children: [ - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.green, - foregroundColor: Colors.white, - shadowColor: Colors.blueAccent, - elevation: 5, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30.0), - ), - padding: - const EdgeInsets.symmetric(horizontal: 20, vertical: 15), - textStyle: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - onPressed: () { - Navigator.push(context, - MaterialPageRoute(builder: (context) => HomePage())); - }, - child: Text("Home Page"), - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.purple, - foregroundColor: Colors.white, - shadowColor: Colors.blueAccent, - elevation: 5, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30.0), - ), - padding: - const EdgeInsets.symmetric(horizontal: 20, vertical: 15), - textStyle: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - onPressed: () { - Navigator.push(context, - MaterialPageRoute(builder: (context) => DomainPage())); - }, - child: Text("Domain Page"), - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: const Color.fromARGB(255, 39, 94, 176), - foregroundColor: Colors.white, - shadowColor: Colors.blueAccent, - elevation: 5, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30.0), - ), - padding: - const EdgeInsets.symmetric(horizontal: 20, vertical: 15), - textStyle: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - onPressed: () { - Navigator.push(context, - MaterialPageRoute(builder: (context) => BottomNav())); - }, - child: Text("Bottom Nav"), - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: const Color.fromARGB(255, 39, 94, 176), - foregroundColor: Colors.white, - shadowColor: Colors.blueAccent, - elevation: 5, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30.0), - ), - padding: - const EdgeInsets.symmetric(horizontal: 20, vertical: 15), - textStyle: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - onPressed: () { - Navigator.push(context, - MaterialPageRoute(builder: (context) => SubmissionPage())); - }, - child: Text("Submission Page"), - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: const Color.fromARGB(255, 39, 94, 176), - foregroundColor: Colors.white, - shadowColor: Colors.blueAccent, // Shadow color - elevation: 5, // Elevation of the button - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30.0), // Rounded corners - ), - padding: EdgeInsets.symmetric( - horizontal: 20, vertical: 15), // Padding inside the button - textStyle: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - onPressed: () { - Navigator.push(context, - MaterialPageRoute(builder: (context) => SubmissionPage())); - }, - child: Text("Submission Page"), - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: const Color.fromARGB(255, 39, 94, 176), - foregroundColor: Colors.white, - shadowColor: Colors.blueAccent, // Shadow color - elevation: 5, // Elevation of the button - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30.0), // Rounded corners - ), - padding: EdgeInsets.symmetric( - horizontal: 20, vertical: 15), // Padding inside the button - textStyle: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - onPressed: () { - Navigator.push(context, - MaterialPageRoute(builder: (context) => SubmissionPage())); - }, - child: Text("Submission Page"), - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.amber, - foregroundColor: Colors.white, - shadowColor: Colors.blueAccent, - elevation: 5, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30.0), - ), - padding: - const EdgeInsets.symmetric(horizontal: 20, vertical: 15), - textStyle: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - onPressed: () { - Navigator.push(context, - MaterialPageRoute(builder: (context) => ConfirmPage())); - }, - child: const Text("Confirm"), - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.deepOrange, - foregroundColor: Colors.white, - shadowColor: Colors.blueAccent, - elevation: 5, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30.0), - ), - padding: - const EdgeInsets.symmetric(horizontal: 20, vertical: 15), - textStyle: const TextStyle( - - padding: EdgeInsets.symmetric( - horizontal: 20, vertical: 15), // Padding inside the button - - textStyle: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => PreviousProjects())); - - Navigator.push(context, - MaterialPageRoute(builder: (context) => SpecificProject())); - }, - child: Text("Previous Projects"), - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Color.fromARGB(255, 50, 7, 120), - foregroundColor: Color.fromARGB(255, 242, 244, 244), - shadowColor: Colors.black12, - elevation: 5, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30.0), - ), - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 15), - textStyle: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - onPressed: () { - Navigator.push( - context, MaterialPageRoute(builder: (context) => Splash())); - }, - child: const Text("SplashScreen"), - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.teal, - foregroundColor: Colors.white, - shadowColor: Colors.blueAccent, - elevation: 5, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30.0), - ), - padding: - const EdgeInsets.symmetric(horizontal: 20, vertical: 15), - textStyle: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ProgressTrackerPage())); - }, - child: const Text("Tracker Page"), - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.teal, - foregroundColor: Colors.white, - shadowColor: Colors.blueAccent, - elevation: 5, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30.0), - ), - padding: - const EdgeInsets.symmetric(horizontal: 20, vertical: 15), - textStyle: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => MgmtPreviousProjects())); - }, - child: const Text("Management Project View Page"), - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Color.fromARGB(255, 50, 7, 120), - foregroundColor: Color.fromARGB(255, 242, 244, 244), - shadowColor: Colors.black12, - elevation: 5, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30.0), - ), - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 15), - textStyle: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - onPressed: () { - Navigator.push(context, - MaterialPageRoute(builder: (context) => Studentview())); - }, - child: Text("Student View"), - ), + buildNavButton(context, "Home Page", Colors.green, const HomePage()), + buildNavButton(context, "Domain Page", Colors.purple, const DomainPage()), + buildNavButton(context, "Theme Page Implementation", Colors.blue, const ThemedPage()), + buildNavButton(context, "Submission Page", Colors.blue, SubmissionPage()), + buildNavButton(context, "Confirm", Colors.amber, const ConfirmPage()), + buildNavButton(context, "Previous Projects", Colors.deepOrange, const PreviousProjects()), + buildNavButton(context, "SplashScreen", Colors.indigo, const Splash()), + buildNavButton(context, "Tracker Page", Colors.teal, const ProgressTrackerPage()), + buildNavButton(context, "Management Project View", Colors.teal, const MgmtPreviousProjects()), + buildNavButton(context, "Student View", Colors.indigo, const Studentview()), + buildNavButton(context, "New Project Detail Page", Colors.indigo, const ProjectDetailPage()), + buildNavButton(context, "Admin fees", const Color.fromARGB(255, 9, 9, 9), const AdminFeedPage()), + buildNavButton(context, "User feed", const Color.fromARGB(255, 200, 198, 49), const UserFeedPage()), ], ), ), diff --git a/frontend/lib/pages/login_page.dart b/frontend/lib/pages/login_page.dart deleted file mode 100644 index 7d3d825..0000000 --- a/frontend/lib/pages/login_page.dart +++ /dev/null @@ -1,107 +0,0 @@ -import 'package:flutter/material.dart'; - -class LoginPage extends StatelessWidget { - const LoginPage({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.black, - body: Padding( - padding: const EdgeInsets.symmetric(horizontal: 32.0), - child: Center( - child: Container( - padding: const EdgeInsets.all(20.0), - decoration: BoxDecoration( - color: Colors.grey[850], - borderRadius: BorderRadius.circular(10.0), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: const Offset(0, 3), - ), - ], - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text( - 'Login with your details', - style: TextStyle( - color: Colors.white, - fontSize: 24, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 20), - TextField( - decoration: InputDecoration( - filled: true, - fillColor: Colors.grey[700], - hintText: 'Username', - hintStyle: const TextStyle(color: Colors.white54), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), - borderSide: BorderSide.none, - ), - ), - ), - const SizedBox(height: 10), - TextField( - decoration: InputDecoration( - filled: true, - fillColor: Colors.grey[700], - hintText: 'Registered Email', - hintStyle: const TextStyle(color: Colors.white54), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), - borderSide: BorderSide.none, - ), - ), - ), - const SizedBox(height: 20), - ElevatedButton( - onPressed: () { - // Handle login action - }, - style: ElevatedButton.styleFrom( - backgroundColor: const Color.fromARGB(255, 53, 156, 19), - fixedSize: const Size(double.infinity, 50), - ), - child: const Text( - 'Login', - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), - ), - ), - ), - bottomNavigationBar: BottomNavigationBar( - backgroundColor: Colors.grey[850], - selectedItemColor: Colors.white, - unselectedItemColor: Colors.grey, - items: const [ - BottomNavigationBarItem( - icon: Icon(Icons.home), - label: 'Home', - ), - BottomNavigationBarItem( - icon: Icon(Icons.person), - label: 'Profile', - ), - BottomNavigationBarItem( - icon: Icon(Icons.settings), - label: 'Settings', - ), - ], - ), - ); - } -} diff --git a/frontend/lib/pages/previous_projects.dart b/frontend/lib/pages/previous_projects.dart deleted file mode 100644 index 021cd31..0000000 --- a/frontend/lib/pages/previous_projects.dart +++ /dev/null @@ -1,532 +0,0 @@ -import 'package:flutter/material.dart'; -import 'specific_project.dart'; - -class PreviousProjects extends StatefulWidget { - const PreviousProjects({super.key}); - - @override - State createState() => _PreviousProjectsState(); -} - -class _PreviousProjectsState extends State { - @override - Widget build(BuildContext context) { - return SafeArea( - child: Scaffold( - appBar: AppBar( - leading: Container( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: IconButton( - onPressed: () { - Navigator.pop(context); - }, - icon: Icon( - Icons.arrow_back_ios, - size: 30, - ), - ), - ), - Expanded( - child: Builder( - builder: (context) => IconButton( - icon: Icon( - Icons.menu, - size: 40, - ), - onPressed: () { - Scaffold.of(context).openDrawer(); - }, - ), - ), - ), - ], - ), - ), - backgroundColor: Colors.white, - actions: [ - IconButton( - onPressed: () {}, - icon: Icon( - Icons.notifications_none, - size: 40, - ), - ) - ], - ), - drawer: Drawer( - child: ListView( - padding: EdgeInsets.zero, - children: [ - const DrawerHeader( - decoration: BoxDecoration( - color: Colors.grey, - ), - child: Text('Drawer Header'), - ), - ListTile( - leading: Icon( - Icons.home, - ), - title: const Text('1'), - onTap: () { - Navigator.pop(context); - }, - ), - ListTile( - leading: Icon( - Icons.train, - ), - title: const Text('2'), - onTap: () { - Navigator.pop(context); - }, - ), - ], - ), - ), - body: SingleChildScrollView( - child: Container( - width: double.infinity, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(25, 15, 15, 0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - 'Welcome back,', - style: TextStyle(fontSize: 18, color: Colors.grey), - ), - Text( - 'Nameeee', - style: TextStyle(fontSize: 25), - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.all(18.0), - child: TextField( - decoration: InputDecoration( - filled: true, - fillColor: Colors.grey[300], - labelText: 'Search all projects ....', - // suffixIcon: Icon(Icons.search), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(25.0), - ), - ), - ), - ), - Container( - width: double.infinity, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox( - height: 10, - ), - Column( - children: [ - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: Colors.grey[300], - ), - height: 90, - width: 350, - child: Padding( - padding: const EdgeInsets.only( - left: 10, - ), - child: Row( - children: [ - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: Colors.grey[800], - ), - height: 70, - width: 70, - ), - SizedBox( - width: 19, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: 15, - ), - Text( - 'DOMAIN', - style: TextStyle(fontSize: 15), - ), - Text('Project Name'), - ], - ) - ], - ), - ), - ), - Container( - height: 30, - width: 320, - child: Align( - alignment: Alignment.topRight, - child: SizedBox( - height: double.infinity, - child: ElevatedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - SpecificProject()) - ); - }, - child: Text( - 'Learn More', - style: TextStyle( - fontSize: 15, color: Colors.grey[800]), - ), - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.zero), - backgroundColor: Colors.grey[400], - ), - ), - ), - ), - ) - ], - ), - SizedBox( - height: 20, - ), - Column( - children: [ - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: Colors.grey[300], - ), - height: 90, - width: 350, - child: Padding( - padding: const EdgeInsets.only( - left: 10, - ), - child: Row( - children: [ - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: Colors.grey[800], - ), - height: 70, - width: 70, - ), - SizedBox( - width: 19, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: 15, - ), - Text( - 'DOMAIN', - style: TextStyle(fontSize: 15), - ), - Text('Project Name'), - ], - ) - ], - ), - ), - ), - Container( - height: 30, - width: 320, - child: Align( - alignment: Alignment.topRight, - child: SizedBox( - height: double.infinity, - child: ElevatedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - SpecificProject()) - ); - }, - child: Text( - 'Learn More', - style: TextStyle( - fontSize: 15, color: Colors.grey[800]), - ), - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.zero), - backgroundColor: Colors.grey[400], - ), - ), - ), - ), - ) - ], - ), - SizedBox( - height: 20, - ), - Column( - children: [ - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: Colors.grey[300], - ), - height: 90, - width: 350, - child: Padding( - padding: const EdgeInsets.only( - left: 10, - ), - child: Row( - children: [ - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: Colors.grey[800], - ), - height: 70, - width: 70, - ), - SizedBox( - width: 19, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: 15, - ), - Text( - 'DOMAIN', - style: TextStyle(fontSize: 15), - ), - Text('Project Name'), - ], - ) - ], - ), - ), - ), - Container( - height: 30, - width: 320, - child: Align( - alignment: Alignment.topRight, - child: SizedBox( - height: double.infinity, - child: ElevatedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - SpecificProject()) - ); - }, - child: Text( - 'Learn More', - style: TextStyle( - fontSize: 15, color: Colors.grey[800]), - ), - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.zero), - backgroundColor: Colors.grey[400], - ), - ), - ), - ), - ) - ], - ), - SizedBox( - height: 20, - ), - Column( - children: [ - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: Colors.grey[300], - ), - height: 90, - width: 350, - child: Padding( - padding: const EdgeInsets.only( - left: 10, - ), - child: Row( - children: [ - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: Colors.grey[800], - ), - height: 70, - width: 70, - ), - SizedBox( - width: 19, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: 15, - ), - Text( - 'DOMAIN', - style: TextStyle(fontSize: 15), - ), - Text('Project Name'), - ], - ) - ], - ), - ), - ), - Container( - height: 30, - width: 320, - child: Align( - alignment: Alignment.topRight, - child: SizedBox( - height: double.infinity, - child: ElevatedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - SpecificProject()) - ); - }, - child: Text( - 'Learn More', - style: TextStyle( - fontSize: 15, color: Colors.grey[800]), - ), - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.zero), - backgroundColor: Colors.grey[400], - ), - ), - ), - ), - ) - ], - ), - SizedBox( - height: 20, - ), - Column( - children: [ - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: Colors.grey[300], - ), - height: 90, - width: 350, - child: Padding( - padding: const EdgeInsets.only( - left: 10, - ), - child: Row( - children: [ - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: Colors.grey[800], - ), - height: 70, - width: 70, - ), - SizedBox( - width: 19, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: 15, - ), - Text( - 'DOMAIN', - style: TextStyle(fontSize: 15), - ), - Text('Project Name'), - ], - ) - ], - ), - ), - ), - Container( - height: 30, - width: 320, - child: Align( - alignment: Alignment.topRight, - child: SizedBox( - height: double.infinity, - child: ElevatedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - SpecificProject()) - ); - }, - child: Text( - 'Learn More', - style: TextStyle( - fontSize: 15, color: Colors.grey[800]), - ), - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.zero), - backgroundColor: Colors.grey[400], - ), - ), - ), - ), - ) - ], - ), - ], - ), - ) - ], - ), - ), - ), - )); - } -} diff --git a/frontend/lib/pages/specific_project.dart b/frontend/lib/pages/specific_project.dart deleted file mode 100644 index ed91409..0000000 --- a/frontend/lib/pages/specific_project.dart +++ /dev/null @@ -1,228 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; -import 'package:readmore/readmore.dart'; -import 'std_details.dart'; -import 'teamcard.dart'; - - -class SpecificProject extends StatefulWidget { - const SpecificProject({super.key}); - - @override - State createState() => _SpecificProjectState(); -} - -class _SpecificProjectState extends State { - @override - Widget build(BuildContext context) { - return SafeArea( - child: Scaffold( - appBar: AppBar( - leading: Container( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: IconButton( - onPressed: () { - Navigator.pop(context); - }, - icon: Icon( - Icons.arrow_back_ios, - size: 30, - ), - ), - ), - Expanded( - child: Builder( - builder: (context) => IconButton( - icon: Icon( - Icons.menu, - size: 40, - ), - onPressed: () { - Scaffold.of(context).openDrawer(); - }, - ), - ), - ), - ], - ), - ), - backgroundColor: Colors.white, - actions: [ - IconButton( - onPressed: () {}, - icon: Icon( - Icons.notifications_none, - size: 40, - ), - ) - ], - ), - drawer: Drawer( - child: ListView( - padding: EdgeInsets.zero, - children: [ - const DrawerHeader( - - decoration: BoxDecoration( - color: Colors.grey, - ), - child: Text('Drawer Header'), - ), - ListTile( - leading: Icon( - Icons.home, - ), - title: const Text('1'), - onTap: () { - Navigator.pop(context); - }, - ), - ListTile( - leading: Icon( - Icons.train, - ), - title: const Text('2'), - onTap: () { - Navigator.pop(context); - }, - ), - ], - ), - ), - body: SingleChildScrollView( - child: Container( - width: double.infinity, - child: Column( - children: [ - SizedBox( - height: 20, - ), - Text( - 'Team Name', - style: TextStyle(fontSize: 30), - ), - Padding( - padding: const EdgeInsets.fromLTRB(28, 5, 28, 5), - child: Container( - height: 200, - width: double.infinity, - decoration: BoxDecoration( - border: Border.all(color: Colors.black26), - borderRadius: BorderRadius.all(Radius.circular(20)) - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Container( - height: 110, - width: 250, - color: Colors.grey[300], - ), - Container( - height: 30, - width: 300, - color: Colors.grey[300], - child: Center(child: Text('github link')), - ) - ], - ), - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(15, 0, 15, 0), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'About:', - style: TextStyle(fontSize: 20), - ), - - Wrap( - children: [ - ReadMoreText( - 'iuegfib hiuewiubvdsuig ugiuvbugiw gviugiugeuiwfhiviuweguihiwue uiguiuehf iuhjdkhu hdfoihfn ohnj ', - trimLines: 2, - trimMode: TrimMode.Line, - trimCollapsedText: "Show more", - trimExpandedText: "Show less", - moreStyle: const TextStyle( - fontWeight: FontWeight.bold, - color: Colors.blue, - ), - lessStyle: const TextStyle( - fontWeight: FontWeight.bold, - color: Colors.blue, - ), - style: const TextStyle( - color: Colors.black54, - ), - ), - ], - ) - - ], - ), - ), - ], - ), - ), - SizedBox( - height: 20, - ), - Row( - children: [ - SizedBox(width: 10,), - Text( - 'Team Members:', - style: TextStyle(fontSize: 20), - ) - ], - ), - - - Container( - height: 190, - - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: STD_details.stdlist.length, - itemBuilder: (context, index) { - return Container( - width: 150, - margin: EdgeInsets.symmetric( - horizontal: 2), - child: TeamCard(std: STD_details.stdlist[index]), - ); - }, - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Text('Upload Pdf'), - Padding( - padding: const EdgeInsets.all(30.0), - child: Container( - height: 120, - width: 250, - color: Colors.grey[300], - ), - )], - ) - ], - ), - ), - ), - ), - ); - } -} diff --git a/frontend/lib/pages/std_details.dart b/frontend/lib/pages/std_details.dart deleted file mode 100644 index ee01093..0000000 --- a/frontend/lib/pages/std_details.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:flutter/material.dart'; -import 'std_class.dart'; - -class STD_details{ - static List stdlist=[ - STD("070","Vaishali","assets/images/elmoo.jpg","https://www.youtube.com/watch?v=dQw4w9WgXcQ","Lead"), - STD("071","Areny","assets/images/elmoo.jpg","https://www.youtube.com/watch?v=dQw4w9WgXcQ","Front-End"), - STD("072","Atul","assets/images/elmoo.jpg","https://www.youtube.com/watch?v=dQw4w9WgXcQ","Back-End"), - STD("073","Vai","assets/images/elmoo.jpg","https://www.youtube.com/watch?v=dQw4w9WgXcQ","UI/UX"), - - ]; -} \ No newline at end of file diff --git a/frontend/lib/pages/user_pages/project_pages/project_display/previous_projects.dart b/frontend/lib/pages/user_pages/project_pages/project_display/previous_projects.dart new file mode 100644 index 0000000..e474abb --- /dev/null +++ b/frontend/lib/pages/user_pages/project_pages/project_display/previous_projects.dart @@ -0,0 +1,202 @@ +import 'package:flutter/material.dart'; +import 'package:dev_track_app/utils/topnavbar.dart'; +import 'package:dev_track_app/routing/previous_projects_routing.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:dev_track_app/theme/colors.dart'; + +class PreviousProjects extends StatefulWidget { + const PreviousProjects({super.key}); + + @override + State createState() => _PreviousProjectsState(); +} + +class _PreviousProjectsState extends State { + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + backgroundColor: AppColors.backgroundLight, + appBar: TopNavBar( + onNotificationTap: () { + // Handle notification tap + }, + ), + body: Column( + children: [ + _buildSearchBar(), + Expanded( + child: CustomScrollView( + slivers: [ + SliverToBoxAdapter( + child: _buildTitle(), + ), + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => _buildProjectCard(projects[index]), + childCount: projects.length, + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildSearchBar() { + return Padding( + padding: const EdgeInsets.all(18.0), + child: SizedBox( + height: 50, + child: TextField( + decoration: InputDecoration( + filled: true, + fillColor: AppColors.backgroundLight, + labelText: 'Search all projects', + prefixIcon: Icon(Icons.search, color: AppColors.neutralDark), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(14.0), + borderSide: const BorderSide(color: Color(0xFF5B2333)), + ), + ), + ), + ), + ); + } + + Widget _buildTitle() { + return Padding( + padding: const EdgeInsets.fromLTRB(25, 15, 15, 0), + child: Text( + 'Explore our projects', + style: GoogleFonts.poppins( + fontSize: 30, + fontWeight: FontWeight.w600, + fontStyle: FontStyle.italic, + color: const Color(0xFF6901AE), + ), + ), + ); + } + + final List> projects = [ + { + "title": "UI/UX 42", + "subtitle": "Budgeting application", + "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + "image": "assets/images/game.png" + }, + { + "title": "Dev Track", + "subtitle": "Project management tool", + "description": "A tool to track developer progress in real-time.", + "image": "assets/images/devtrack.png" + }, + { + "title": "E-commerce App", + "subtitle": "Shopping made easy", + "description": "An intuitive mobile shopping experience.", + "image": "assets/images/ecommerce.png" + }, + { + "title": "E-commerce App", + "subtitle": "Shopping made easy", + "description": "An intuitive mobile shopping experience.", + "image": "assets/images/ecommerce.png" + }, + { + "title": "E-commerce App", + "subtitle": "Shopping made easy", + "description": "An intuitive mobile shopping experience.", + "image": "assets/images/ecommerce.png" + }, + ]; + + Widget _buildProjectCard(Map project) { + return Container( + padding: const EdgeInsets.all(24), + margin: const EdgeInsets.symmetric(vertical: 16, horizontal: 15), + decoration: BoxDecoration( + color: const Color(0xFF6901AE), + borderRadius: BorderRadius.circular(4), + ), + child: Row( + children: [ + Flexible( + flex: 1, + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.asset( + project["image"]!, + fit: BoxFit.cover, + ), + ), + ), + ), + const SizedBox(width: 15), + Expanded( + flex: 3, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + project["title"]!, + style: GoogleFonts.poppins( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + Text( + project["subtitle"]!, + style: GoogleFonts.poppins( + color: Colors.white, + fontSize: 13, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 4), + Text( + project["description"]!, + style: GoogleFonts.poppins( + color: Colors.white, + fontSize: 10, + ), + ), + const SizedBox(height: 8), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + foregroundColor: Colors.black, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + padding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 10), + textStyle: GoogleFonts.poppins( + fontSize: 10, + fontWeight: FontWeight.bold, + ), + ), + onPressed: () { + PreviousProjectsRouting.pushToSpecificProject(context); + }, + child: const Text("Explore"), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/frontend/lib/pages/user_pages/project_pages/project_display/specific_project.dart b/frontend/lib/pages/user_pages/project_pages/project_display/specific_project.dart new file mode 100644 index 0000000..d8ef7a4 --- /dev/null +++ b/frontend/lib/pages/user_pages/project_pages/project_display/specific_project.dart @@ -0,0 +1,219 @@ +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:dev_track_app/theme/colors.dart'; +import 'package:dev_track_app/utils/topnavbar.dart'; +import 'package:dev_track_app/models/new_specific_projectModels.dart'; + +class TeamMemberCard extends StatelessWidget { + final TeamMember member; + + const TeamMemberCard({Key? key, required this.member}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Card( + elevation: 2, + color: AppColors.primaryLight.withOpacity(0.9), + child: Padding( + padding: const EdgeInsets.all(18), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.network( + member.imageUrl, + width: 100, + height: 100, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) => + const Icon(Icons.person, size: 100), + ), + ), + const SizedBox(height: 8), + Text( + member.name, + style: Theme.of(context) + .textTheme + .titleMedium + ?.copyWith(color: AppColors.textPrimaryLight), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 8), + InkWell( + onTap: () async { + final Uri url = Uri.parse(member.linkedInUrl); + if (await canLaunchUrl(url)) { + await launchUrl(url, mode: LaunchMode.externalApplication); + } + }, + child: Row( + mainAxisSize: MainAxisSize.min, + children: const [ + Icon(Icons.link, size: 16, color: AppColors.textPrimaryLight), + SizedBox(width: 4), + Text( + "LinkedIn", + style: TextStyle(color: AppColors.textPrimaryLight), + ), + ], + ), + ), + ], + ), + ), + ); + } +} + +class ProjectDetailPage extends StatelessWidget { + const ProjectDetailPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + // Use the dummy data from models + final projectData = dummyProjectData; + + return Scaffold( + backgroundColor: AppColors.backgroundLight, + appBar: TopNavBar( + onNotificationTap: () { + // Open notifications page + }, + ), + body: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 16), + Text( + projectData.projectName, + style: Theme.of(context).textTheme.headlineLarge, + ), + const SizedBox(height: 4), + + Text( + projectData.projectCycle, + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 16), + + Container( + height: 150, + width: double.infinity, + decoration: BoxDecoration( + color: AppColors.primaryLight.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.network( + dummyProjectData.projectImage, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + debugPrint("Error loading image: $error"); + return const Icon( + Icons.image_search, + size: 50, + color: Colors.grey, + ); + }, + ), + ), + ), + const SizedBox(height: 16), + + Row( + children: [ + Expanded( + child: LinearProgressIndicator( + value: projectData.progress, + minHeight: 6, + color: Theme.of(context).colorScheme.primary, + backgroundColor: Colors.grey[300], + ), + ), + const SizedBox(width: 8), + Text("${(projectData.progress * 100).round()}%"), + ], + ), + const SizedBox(height: 24), + + Text( + "About", + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 8), + + Text( + projectData.description, + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + height: 1.4, + ), + textAlign: TextAlign.justify, + ), + const SizedBox(height: 24), + + Row( + children: [ + ElevatedButton( + onPressed: () async { + final Uri url = Uri.parse(projectData.githubLink); + if (await canLaunchUrl(url)) { + await launchUrl(url, + mode: LaunchMode.externalApplication); + } + }, + child: const Text("Github-Link"), + ), + const SizedBox(width: 16), + ElevatedButton( + onPressed: () async { + final Uri url = Uri.parse(projectData.projectLink); + if (await canLaunchUrl(url)) { + await launchUrl(url, + mode: LaunchMode.externalApplication); + } + }, + child: const Text("Link"), + ), + ], + ), + const SizedBox(height: 24), + + Text( + "Team Members", + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 16), + + // Horizontal scrollable team members + SizedBox( + height: 220, // Fixed height for the scroll view + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: projectData.teamMembers.length, + itemBuilder: (context, index) { + return Padding( + padding: EdgeInsets.only( + right: 16, + left: index == 0 ? 0 : 0, + ), + child: SizedBox( + width: 160, // Fixed width for each card + child: TeamMemberCard( + member: projectData.teamMembers[index]), + ), + ); + }, + ), + ), + const SizedBox(height: 24), + ], + ), + ), + ); + } +} diff --git a/frontend/lib/pages/teamcard.dart b/frontend/lib/pages/user_pages/project_pages/project_display/teamcard.dart similarity index 81% rename from frontend/lib/pages/teamcard.dart rename to frontend/lib/pages/user_pages/project_pages/project_display/teamcard.dart index 5ec6d8b..bfc0eb8 100644 --- a/frontend/lib/pages/teamcard.dart +++ b/frontend/lib/pages/user_pages/project_pages/project_display/teamcard.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'std_class.dart'; +import '../../../../models/std_class.dart'; class TeamCard extends StatefulWidget { final STD std; @@ -17,7 +17,7 @@ class _TeamCardState extends State { scrollDirection: Axis.horizontal, child: Container( child: SizedBox( - width:150, + width: 150, child: Card( clipBehavior: Clip.antiAlias, elevation: 0, @@ -32,14 +32,17 @@ class _TeamCardState extends State { height: 150, decoration: BoxDecoration( border: Border.all(color: Colors.black26), - borderRadius: BorderRadius.circular(10), // Adjust the radius as needed + borderRadius: BorderRadius.circular( + 10), // Adjust the radius as needed ), child: ClipRRect( - borderRadius: BorderRadius.circular(10), // Ensure the image fits within the rounded corners + borderRadius: BorderRadius.circular( + 10), // Ensure the image fits within the rounded corners child: Image.asset( widget.std.profilepic, width: double.infinity, - fit: BoxFit.fill, // Use cover for better aspect ratio handling + fit: BoxFit + .fill, // Use cover for better aspect ratio handling ), ), ), diff --git a/frontend/lib/pages/submission_page.dart b/frontend/lib/pages/user_pages/project_pages/submission_page/submission_page.dart similarity index 100% rename from frontend/lib/pages/submission_page.dart rename to frontend/lib/pages/user_pages/project_pages/submission_page/submission_page.dart diff --git a/frontend/lib/pages/user_pages/project_pages/submission_page/widgets/drop_down_button.dart b/frontend/lib/pages/user_pages/project_pages/submission_page/widgets/drop_down_button.dart new file mode 100644 index 0000000..8b36e63 --- /dev/null +++ b/frontend/lib/pages/user_pages/project_pages/submission_page/widgets/drop_down_button.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:dev_track_app/models/submission_model.dart'; + +class DropdownButtonApp extends StatefulWidget { + const DropdownButtonApp({super.key}); + + @override + State createState() => _DropdownButtonAppState(); +} + +class _DropdownButtonAppState extends State { + String dropdownValue = submissionList.first; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: DropdownButton( + value: dropdownValue, + isExpanded: true, + icon: const Icon(Icons.arrow_downward, color: Colors.white), + elevation: 16, + style: const TextStyle(color: Colors.white), + dropdownColor: Colors.grey[800], + onChanged: (String? newValue) { + setState(() { + dropdownValue = newValue!; + }); + }, + items: submissionList.map>((String value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + ), + ); + } +} diff --git a/frontend/lib/pages/studentview.dart b/frontend/lib/pages/user_pages/studentview.dart similarity index 99% rename from frontend/lib/pages/studentview.dart rename to frontend/lib/pages/user_pages/studentview.dart index f4bb2a2..805f7f1 100644 --- a/frontend/lib/pages/studentview.dart +++ b/frontend/lib/pages/user_pages/studentview.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:image_picker/image_picker.dart'; import 'package:shared_preferences/shared_preferences.dart'; // For local storage -import 'package:dev_track_app/pages/imageuploadpage.dart'; // Ensure this is the correct path +import 'package:dev_track_app/utils/imageuploadpage.dart'; // Ensure this is the correct path class Studentview extends StatefulWidget { const Studentview({super.key}); diff --git a/frontend/lib/pages/tracker.dart b/frontend/lib/pages/user_pages/tracker.dart similarity index 91% rename from frontend/lib/pages/tracker.dart rename to frontend/lib/pages/user_pages/tracker.dart index 9fc243d..37d6e57 100644 --- a/frontend/lib/pages/tracker.dart +++ b/frontend/lib/pages/user_pages/tracker.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import '../../logic/tracker_logic.dart'; void main() { runApp(const MaterialApp( @@ -16,34 +17,29 @@ class ProgressTrackerPage extends StatefulWidget { class ProgressTrackerPageState extends State with SingleTickerProviderStateMixin { - List isExpandedList = []; // Initialize an empty list for expanded state - List progressControllers = - []; // Initialize an empty list for controllers + late ProgressTrackerLogic _logic; late AnimationController _controller; late Animation _animation; @override void initState() { super.initState(); + + // Instantiate the logic class, which sets up expanded lists & controllers + _logic = ProgressTrackerLogic(numberOfMembers: 4); + + //set up animation _controller = AnimationController( duration: const Duration(seconds: 8), vsync: this, )..repeat(reverse: true); _animation = CurvedAnimation(parent: _controller, curve: Curves.easeInOut); - - // Initialize lists based on the number of items - int numberOfMembers = 4; - isExpandedList = List.generate(numberOfMembers, (_) => false); - progressControllers = - List.generate(numberOfMembers, (_) => TextEditingController()); } @override void dispose() { _controller.dispose(); - for (var controller in progressControllers) { - controller.dispose(); - } + _logic.dispose(); super.dispose(); } @@ -129,7 +125,7 @@ class ProgressTrackerPageState extends State const SizedBox(height: 20), Expanded( child: ListView.builder( - itemCount: isExpandedList.length, // Use the length of the list + itemCount: _logic.isExpandedList.length, // Use the length of the list itemBuilder: (context, index) { return Card( elevation: 4, @@ -139,7 +135,7 @@ class ProgressTrackerPageState extends State expandedHeaderPadding: const EdgeInsets.all(0), expansionCallback: (int itemIndex, bool isExpanded) { setState(() { - isExpandedList[index] = !isExpandedList[index]; + _logic.isExpandedList[index] = !_logic.isExpandedList[index]; }); }, children: [ @@ -187,7 +183,7 @@ class ProgressTrackerPageState extends State ), const SizedBox(height: 16), TextField( - controller: progressControllers[index], + controller: _logic.progressControllers[index], decoration: const InputDecoration( labelText: 'Add progress here...', border: OutlineInputBorder(), @@ -213,7 +209,7 @@ class ProgressTrackerPageState extends State ], ), ), - isExpanded: isExpandedList[index], + isExpanded: _logic.isExpandedList[index], ), ], ), @@ -229,7 +225,7 @@ class ProgressTrackerPageState extends State void _showEditDialog(BuildContext context, int index) { TextEditingController editController = - TextEditingController(text: progressControllers[index].text); + TextEditingController(text: _logic.progressControllers[index].text); showDialog( context: context, @@ -254,7 +250,7 @@ class ProgressTrackerPageState extends State ElevatedButton( onPressed: () { setState(() { - progressControllers[index].text = editController.text; + _logic.progressControllers[index].text = editController.text; }); Navigator.of(context).pop(); ScaffoldMessenger.of(context).showSnackBar( diff --git a/frontend/lib/pages/user_pages/user_dummy_home.dart b/frontend/lib/pages/user_pages/user_dummy_home.dart new file mode 100644 index 0000000..fefaef5 --- /dev/null +++ b/frontend/lib/pages/user_pages/user_dummy_home.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +class UserDummyHome extends StatefulWidget { + const UserDummyHome({super.key}); + + @override + State createState() => _UserDummyHomeState(); +} + +class _UserDummyHomeState extends State { + @override + Widget build(BuildContext context) { + return const Scaffold( + body: Center( + child: Text('user home'), + ), + ); + } +} diff --git a/frontend/lib/pages/user_pages/user_feed_page.dart b/frontend/lib/pages/user_pages/user_feed_page.dart new file mode 100644 index 0000000..1024410 --- /dev/null +++ b/frontend/lib/pages/user_pages/user_feed_page.dart @@ -0,0 +1,214 @@ +import 'package:flutter/material.dart'; + + + +class UserFeedPage extends StatefulWidget { + const UserFeedPage({super.key}); + + @override + State createState() => _UserFeedPageState(); +} + +class Post { + final String details; + + Post({required this.details}); +} + + + +class _UserFeedPageState extends State { + final List posts = [ + Post(details: "Post 1 details"), + Post(details: 'Post 2 details'), + Post(details: 'Post 3 details'), + ]; + + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + backgroundColor: Colors.white, + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildTopBar(), + const SizedBox(height: 10), + _buildHeader(), + const SizedBox(height: 10), + _buildTabBar(), + const SizedBox(height: 10), + Expanded( + child: ListView.builder( + itemCount: posts.length, + itemBuilder: (context, index) => PostCard( + post: posts[index], + onViewMore: () => showPopup(context, posts[index]), + ), + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildTopBar() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.black), + onPressed: () {}, + ), + IconButton( + icon: const Icon(Icons.notifications, color: Colors.black), + onPressed: () {}, + ), + ], + ); + } + + void showPopup(BuildContext context, Post post) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text("Post Details"), + content: Text(post.details), // fetchhh post details..... + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text("Close"), + ), + ], + ); + }, + ); + } + + Widget _buildHeader() { + return const Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("WELCOME BACK"), + Text( + "Bharathan", + style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold), + ), + ], + ); + } + + Widget _buildTabBar() { + return Container( + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildTab("Primary", isSelected: true), + _buildTab("Secondary"), + _buildTab("Ternary"), + ], + ), + ); + } + + Widget _buildTab(String title, {bool isSelected = false}) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + child: Text( + title, + style: TextStyle( + fontSize: 16, + fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, + color: isSelected ? Colors.purple : Colors.black, + ), + ), + ); + } +} + + + +class PostCard extends StatelessWidget { + final Post post; + final VoidCallback onViewMore; + + const PostCard({super.key, required this.post, required this.onViewMore}); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 8), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const CircleAvatar( + backgroundColor: Colors.grey, + radius: 20, + ), + const SizedBox(width: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + Text( + "Name", + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), + ), + Text( + "2 Days ago • DD/month Time", + style: TextStyle(fontSize: 12, color: Colors.grey), + ), + ], + ), + ], + ), + const SizedBox(height: 10), + Text( + post.details, + style: const TextStyle(fontSize: 14), + ), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: List.generate( + 3, + (index) => Container( + margin: const EdgeInsets.only(right: 8), + height: 10, + width: 30, + color: Colors.grey[300], + ), + ), + ), + FloatingActionButton( + tooltip: 'View More', + onPressed: onViewMore, + backgroundColor: Colors.purple, + mini: true, + child: const Icon(Icons.arrow_forward, color: Colors.white, size: 18), + ), + ], + ), + ], + ), + ); + } +} diff --git a/frontend/lib/routing/previous_projects_routing.dart b/frontend/lib/routing/previous_projects_routing.dart new file mode 100644 index 0000000..190c05d --- /dev/null +++ b/frontend/lib/routing/previous_projects_routing.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; +// import '../To-Be-Discarded/specific_project.dart'; +import 'package:dev_track_app/pages/user_pages/project_pages/project_display/specific_project.dart'; + +class PreviousProjectsRouting { + static void pop(BuildContext context) { + Navigator.pop(context); + } + + static void pushToSpecificProject(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => ProjectDetailPage()), + ); + } +} diff --git a/frontend/lib/routing/routes.dart b/frontend/lib/routing/routes.dart new file mode 100644 index 0000000..e69de29 diff --git a/frontend/lib/theme/colors.dart b/frontend/lib/theme/colors.dart new file mode 100644 index 0000000..2e50383 --- /dev/null +++ b/frontend/lib/theme/colors.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +class AppColors { + // Light Theme Colors + static const Color primaryLight = Color(0xFF6200EA); + static const Color secondaryLight = Color(0xFF03DAC6); + static const Color backgroundLight = Color.fromARGB(255, 255, 255, 255); + static const Color textPrimaryLight = Color.fromARGB(255, 255, 255, 255); + static const Color textSecondaryLight = Color(0xFF757575); + static const Color accentLight = Color(0xFFFFC107); + static const Color neutralLight = Color(0xFF666666); + + // Dark Theme Colors (Based on MUI Theme) + static const Color primaryDark = Color(0xFF1F2A40); + static const Color secondaryDark = Color(0xFF4cceac); + static const Color backgroundDark = Color(0xFF141b2d); + static const Color textPrimaryDark = Color(0xFFE0E0E0); + static const Color textSecondaryDark = Color(0xFFA3A3A3); + static const Color accentDark = Color(0xFF6870fa); + static const Color neutralDark = Color(0xFF3d3d3d); +} diff --git a/frontend/lib/pages/splashscreen.dart b/frontend/lib/theme/splashscreen.dart similarity index 100% rename from frontend/lib/pages/splashscreen.dart rename to frontend/lib/theme/splashscreen.dart diff --git a/frontend/lib/theme/theme.dart b/frontend/lib/theme/theme.dart new file mode 100644 index 0000000..bf3d8dc --- /dev/null +++ b/frontend/lib/theme/theme.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'colors.dart'; + +class AppTheme { + // Light Theme + static final ThemeData lightTheme = ThemeData( + brightness: Brightness.light, + primaryColor: AppColors.primaryLight, + scaffoldBackgroundColor: AppColors.backgroundLight, + colorScheme: ColorScheme.light( + primary: AppColors.primaryLight, + secondary: AppColors.secondaryLight, + background: AppColors.backgroundLight, + onBackground: AppColors.textPrimaryLight, + surface: AppColors.backgroundLight, + onSurface: AppColors.textPrimaryLight, + ), + textTheme: const TextTheme( + displayLarge: TextStyle( + fontSize: 40, + fontWeight: FontWeight.bold, + color: AppColors.textPrimaryLight), + displayMedium: TextStyle( + fontSize: 32, + fontWeight: FontWeight.bold, + color: AppColors.textPrimaryLight), + displaySmall: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: AppColors.textPrimaryLight), + headlineLarge: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: AppColors.textPrimaryLight), + titleLarge: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColors.textPrimaryLight), + bodyLarge: TextStyle(fontSize: 14, color: AppColors.textSecondaryLight), + ), + ); + + // Dark Theme + static final ThemeData darkTheme = ThemeData( + brightness: Brightness.dark, + primaryColor: AppColors.primaryDark, + scaffoldBackgroundColor: AppColors.backgroundDark, + colorScheme: ColorScheme.dark( + primary: AppColors.primaryDark, + secondary: AppColors.secondaryDark, + background: AppColors.backgroundDark, + onBackground: AppColors.textPrimaryDark, + surface: AppColors.backgroundDark, + onSurface: AppColors.textPrimaryDark, + ), + textTheme: const TextTheme( + displayLarge: TextStyle( + fontSize: 40, + fontWeight: FontWeight.bold, + color: AppColors.textPrimaryDark), + displayMedium: TextStyle( + fontSize: 32, + fontWeight: FontWeight.bold, + color: AppColors.textPrimaryDark), + displaySmall: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: AppColors.textPrimaryDark), + headlineLarge: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: AppColors.textPrimaryDark), + titleLarge: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColors.textPrimaryDark), + bodyLarge: TextStyle(fontSize: 14, color: AppColors.textSecondaryDark), + ), + ); +} diff --git a/frontend/lib/utils/bottom_nav_bar.dart b/frontend/lib/utils/bottom_nav_bar.dart new file mode 100644 index 0000000..8e0baae --- /dev/null +++ b/frontend/lib/utils/bottom_nav_bar.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; + +class BottomNavBar extends StatelessWidget { + const BottomNavBar({super.key}); + + @override + Widget build(BuildContext context) { + return BottomNavigationBar( + backgroundColor: Colors.grey[850], + selectedItemColor: Colors.white, + unselectedItemColor: Colors.grey, + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.home), + label: 'Home', + ), + BottomNavigationBarItem( + icon: Icon(Icons.person), + label: 'Profile', + ), + BottomNavigationBarItem( + icon: Icon(Icons.settings), + label: 'Settings', + ), + ], + ); + } +} diff --git a/frontend/lib/utils/custom_button.dart b/frontend/lib/utils/custom_button.dart new file mode 100644 index 0000000..5e466af --- /dev/null +++ b/frontend/lib/utils/custom_button.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; + +class CustomButton extends StatelessWidget { + final String text; + final VoidCallback onPressed; + + const CustomButton({super.key, required this.text, required this.onPressed}); + + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: onPressed, + style: ElevatedButton.styleFrom( + backgroundColor: const Color.fromARGB(255, 53, 156, 19), + fixedSize: const Size(120, 50), + ), + child: Text( + text, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ); + } +} diff --git a/frontend/lib/pages/imageuploadpage.dart b/frontend/lib/utils/imageuploadpage.dart similarity index 100% rename from frontend/lib/pages/imageuploadpage.dart rename to frontend/lib/utils/imageuploadpage.dart diff --git a/frontend/lib/utils/topnavbar.dart b/frontend/lib/utils/topnavbar.dart new file mode 100644 index 0000000..3b89740 --- /dev/null +++ b/frontend/lib/utils/topnavbar.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import "package:dev_track_app/theme/theme.dart"; +import 'package:dev_track_app/theme/colors.dart'; + +class TopNavBar extends StatelessWidget implements PreferredSizeWidget { + const TopNavBar({ + Key? key, + required this.onNotificationTap, + }) : super(key: key); + + final VoidCallback onNotificationTap; + + @override + Widget build(BuildContext context) { + return AppBar( + backgroundColor: AppColors.backgroundLight, + elevation: 0, + leading: IconButton( + icon: const Icon( + Icons.arrow_back, + color: Colors.black, + ), + onPressed: () { + // This will automatically handle the navigation stack + Navigator.pop(context); + }, + ), + actions: [ + IconButton( + icon: const Icon( + Icons.notifications, + color: Colors.black, + ), + onPressed: onNotificationTap, + ), + const SizedBox(width: 8), // Adds some padding to the right + ], + ); + } + + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); +} diff --git a/frontend/lib/utils/utils.dart b/frontend/lib/utils/utils.dart new file mode 100644 index 0000000..e69de29 diff --git a/frontend/macos/Flutter/GeneratedPluginRegistrant.swift b/frontend/macos/Flutter/GeneratedPluginRegistrant.swift index d993eb7..aa53084 100644 --- a/frontend/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/frontend/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,12 +5,14 @@ import FlutterMacOS import Foundation +import file_picker import file_selector_macos import path_provider_foundation import shared_preferences_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) diff --git a/frontend/pubspec.lock b/frontend/pubspec.lock index ca991fa..d2a78e1 100644 --- a/frontend/pubspec.lock +++ b/frontend/pubspec.lock @@ -49,30 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.4+2" - cross_file: - dependency: transitive - description: - name: cross_file - sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" - url: "https://pub.dev" - source: hosted - version: "0.3.4+2" - cross_file: - dependency: transitive - description: - name: cross_file - sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" - url: "https://pub.dev" - source: hosted - version: "0.3.4+2" crypto: dependency: transitive description: name: crypto - sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.6" cupertino_icons: dependency: "direct main" description: @@ -81,6 +65,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dio: + dependency: "direct main" + description: + name: dio + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + url: "https://pub.dev" + source: hosted + version: "5.8.0+1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: e485c7a39ff2b384fa1d7e09b4e25f755804de8384358049124830b04fc4f93a + url: "https://pub.dev" + source: hosted + version: "2.1.0" fake_async: dependency: transitive description: @@ -101,26 +101,34 @@ packages: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: cacfdc5abe93e64d418caa9256eef663499ad791bb688d9fd12c85a311968fba url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "8.3.2" file_selector_linux: dependency: transitive description: name: file_selector_linux - sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" url: "https://pub.dev" source: hosted - version: "0.9.2+1" + version: "0.9.3+2" file_selector_macos: dependency: transitive description: name: file_selector_macos - sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 + sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" url: "https://pub.dev" source: hosted - version: "0.9.4" + version: "0.9.4+2" file_selector_platform_interface: dependency: transitive description: @@ -133,26 +141,10 @@ packages: dependency: transitive description: name: file_selector_windows - sha256: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69" + sha256: "8f5d2f6590d51ecd9179ba39c64f722edc15226cc93dcc8698466ad36a4a85a4" url: "https://pub.dev" source: hosted - version: "0.9.3+2" - file_picker: - dependency: "direct main" - description: - name: file_picker - sha256: "167bb619cdddaa10ef2907609feb8a79c16dfa479d3afaf960f8e223f754bf12" - url: "https://pub.dev" - source: hosted - version: "8.1.2" - file_picker: - dependency: "direct main" - description: - name: file_picker - sha256: "167bb619cdddaa10ef2907609feb8a79c16dfa479d3afaf960f8e223f754bf12" - url: "https://pub.dev" - source: hosted - version: "8.1.2" + version: "0.9.3+3" flutter: dependency: "direct main" description: flutter @@ -174,22 +166,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.22" - flutter_plugin_android_lifecycle: - dependency: transitive - description: - name: flutter_plugin_android_lifecycle - sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda" - url: "https://pub.dev" - source: hosted - version: "2.0.22" - flutter_plugin_android_lifecycle: - dependency: transitive - description: - name: flutter_plugin_android_lifecycle - sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda" - url: "https://pub.dev" - source: hosted - version: "2.0.22" flutter_test: dependency: "direct dev" description: flutter @@ -212,10 +188,10 @@ packages: dependency: transitive description: name: http - sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.3.0" http_parser: dependency: transitive description: @@ -244,18 +220,18 @@ packages: dependency: transitive description: name: image_picker_for_web - sha256: "65d94623e15372c5c51bebbcb820848d7bcb323836e12dfdba60b5d3a8b39e50" + sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.6" image_picker_ios: dependency: transitive description: name: image_picker_ios - sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447" + sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" url: "https://pub.dev" source: hosted - version: "0.8.12" + version: "0.8.12+2" image_picker_linux: dependency: transitive description: @@ -268,18 +244,18 @@ packages: dependency: transitive description: name: image_picker_macos - sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" + sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1" url: "https://pub.dev" source: hosted - version: "0.2.1+1" + version: "0.2.1+2" image_picker_platform_interface: dependency: transitive description: name: image_picker_platform_interface - sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" + sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.10.1" image_picker_windows: dependency: transitive description: @@ -292,36 +268,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" - url: "https://pub.dev" - source: hosted - version: "10.0.4" - - - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted version: "10.0.5" - - leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted version: "3.0.5" - - - main - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" - url: "https://pub.dev" - source: hosted - version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -350,30 +308,34 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: name: mime - sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" url: "https://pub.dev" source: hosted - version: "1.15.0" - version: "1.15.0" - - - version: "1.0.6" + version: "2.0.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" path: dependency: transitive description: @@ -386,10 +348,10 @@ packages: dependency: transitive description: name: path_provider - sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" path_provider_android: dependency: transitive description: @@ -402,10 +364,10 @@ packages: dependency: transitive description: name: path_provider_foundation - sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" path_provider_linux: dependency: transitive description: @@ -442,10 +404,10 @@ packages: dependency: transitive description: name: permission_handler_android - sha256: "76e4ab092c1b240d31177bb64d2b0bea43f43d0e23541ec866151b9f7b2490fa" + sha256: "71bbecfee799e65aff7c744761a57e817e73b738fedf62ab7afd5593da21f9f1" url: "https://pub.dev" source: hosted - version: "12.0.12" + version: "12.0.13" permission_handler_apple: dependency: transitive description: @@ -458,18 +420,18 @@ packages: dependency: transitive description: name: permission_handler_html - sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851 + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" url: "https://pub.dev" source: hosted - version: "0.1.3+2" + version: "0.1.3+5" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: fe0ffe274d665be8e34f9c59705441a7d248edebbe5d9e3ec2665f88b79358ea + sha256: e9c8eadee926c4532d0305dff94b85bf961f16759c3af791486613152af4b4f9 url: "https://pub.dev" source: hosted - version: "4.2.2" + version: "4.2.3" permission_handler_windows: dependency: transitive description: @@ -482,10 +444,10 @@ packages: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.6" plugin_platform_interface: dependency: transitive description: @@ -494,6 +456,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + provider: + dependency: "direct main" + description: + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" + source: hosted + version: "6.1.2" readmore: dependency: "direct main" description: @@ -506,10 +476,10 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051" + sha256: "95f9997ca1fb9799d494d0cb2a780fd7be075818d59f00c43832ed112b158a82" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.3" shared_preferences_android: dependency: transitive description: @@ -522,10 +492,10 @@ packages: dependency: transitive description: name: shared_preferences_foundation - sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.5.4" shared_preferences_linux: dependency: transitive description: @@ -607,10 +577,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" typed_data: dependency: transitive description: @@ -623,10 +593,10 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.3.1" url_launcher_android: dependency: transitive description: @@ -639,26 +609,26 @@ packages: dependency: transitive description: name: url_launcher_ios - sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e + sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.2" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.2" url_launcher_platform_interface: dependency: transitive description: @@ -679,10 +649,10 @@ packages: dependency: transitive description: name: url_launcher_windows - sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.4" vector_math: dependency: transitive description: @@ -695,24 +665,18 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" - url: "https://pub.dev" - source: hosted - version: "14.2.1" - - sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.2.4" web: dependency: transitive description: name: web - sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.1.0" win32: dependency: transitive description: @@ -720,17 +684,15 @@ packages: sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "5.5.4" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.1.0" sdks: dart: ">=3.4.0 <4.0.0" flutter: ">=3.22.0" - dart: ">=3.4.0 <4.0.0" - flutter: ">=3.22.0" diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml index 404d869..587953f 100644 --- a/frontend/pubspec.yaml +++ b/frontend/pubspec.yaml @@ -37,10 +37,13 @@ dependencies: cupertino_icons: ^1.0.6 google_fonts: ^6.2.1 readmore: ^3.0.0 - url_launcher: ^6.3.0 + url_launcher: ^6.3.1 image_picker: ^1.1.2 shared_preferences: ^2.0.13 - permission_handler: ^11.3.1 file_picker: ^8.1.2 + permission_handler: ^11.3.1 + file_picker: ^8.1.2 + dio: ^5.3.2 + provider: ^6.0.5 dev_dependencies: flutter_test: @@ -51,7 +54,7 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^4.0.0 + flutter_lints: 4.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/makefile b/makefile new file mode 100644 index 0000000..e7d3581 --- /dev/null +++ b/makefile @@ -0,0 +1,37 @@ +# Variables +MODE ?= local + +# Echo color +GREEN = \033[0;32m +BLUE = \033[0;34m +YELLOW = \033[0;33m +NC = \033[0m + +run: + APP_MODE=$(MODE) python manage.py runserver + +# fmt: +# black . + +debug: + APP_MODE=$(MODE) python -m debugpy --listen 5678 --wait-for-client manage.py runserver + +mm: + APP_MODE=$(MODE) python manage.py makemigrations + +migrate: + APP_MODE=$(MODE) python manage.py migrate + +su: + APP_MODE=$(MODE) python manage.py createsuperuser + + +# collectstatic: +# APP_MODE=$(MODE) python manage.py collectstatic + +clear_db: + find . -path "*/migrations/*.py" -not -name "__init__.py" -delete + find . -path "*/migrations/*.pyc" -delete + +freeze: + pip freeze > requirements.txt diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index bcd856c..0000000 Binary files a/requirements.txt and /dev/null differ