diff --git a/backend-core/config/settings.py b/backend-core/config/settings.py index 6e387f4..204d67e 100644 --- a/backend-core/config/settings.py +++ b/backend-core/config/settings.py @@ -43,7 +43,7 @@ SECRET_KEY = env('SECRET_KEY') # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = env('DEBUG') env_allowed_hosts = os.environ.get("ALLOWED_HOSTS","") diff --git a/backend-core/missions/views.py b/backend-core/missions/views.py index a0cf91f..e3df2ba 100644 --- a/backend-core/missions/views.py +++ b/backend-core/missions/views.py @@ -492,6 +492,7 @@ def chat_room(request: HttpRequest, mission_id: int, room_id: int) -> HttpRespon can_confirm_performer = is_author and mission.status == "PENDING_APPROVAL" show_complete_btn = is_author and mission.status == "MATCHED" blockable_user = {"id": other_user.id, "username": other_user.username} if other_user else None + mission_end = mission.status == "COMPLETED" return render( request, @@ -507,6 +508,7 @@ def chat_room(request: HttpRequest, mission_id: int, room_id: int) -> HttpRespon "show_complete_btn": show_complete_btn, "blockable_user": blockable_user, "other_user": other_user, + "mission_end" : mission_end }, ) diff --git a/backend-core/static/chat/js/room.js b/backend-core/static/chat/js/room.js index 3cd13e9..743001f 100644 --- a/backend-core/static/chat/js/room.js +++ b/backend-core/static/chat/js/room.js @@ -99,6 +99,11 @@ class ChatClient { acceptBtn.addEventListener('click', () => this.acceptMission()); } + const reviewBtn = document.getElementById('reviewPageBtn'); + if(reviewBtn) { + reviewBtn.addEventListener('click',() => window.location.href = `/api/users/review_page/${this.missionId}/`) + } + // 메시지 전송 if (this.sendBtn) { this.sendBtn.addEventListener('click', () => this.sendMessage()); @@ -237,6 +242,7 @@ class ChatClient { if (res.ok && data.success) { alert(data.message || '미션이 완료되었습니다.'); if (btn) btn.remove(); + window.location.href = `/api/users/review_page/${this.missionId}/` } else { alert(data.error || '미션 완료에 실패했습니다.'); btn.disabled = false; diff --git a/backend-core/static/users/css/announcement.css b/backend-core/static/users/css/announcement.css new file mode 100644 index 0000000..46ace1f --- /dev/null +++ b/backend-core/static/users/css/announcement.css @@ -0,0 +1,130 @@ +/* 가로 393px 고정 및 모바일 인터페이스 최적화 */ +body { + margin: 0; + padding: 0; + background-color: #f8f9fa; /* 외부 배경은 차분하게 */ + display: flex; + justify-content: center; + font-family: 'Pretendard', -apple-system, BlinkMacSystemFont, system-ui, Roboto, sans-serif; + -webkit-font-smoothing: antialiased; +} + +.container { + width: 393px; /* 팀장님의 물리적 규격 준수 */ + min-height: 100vh; + background-color: #ffffff; + box-sizing: border-box; + display: flex; + flex-direction: column; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.05); +} + +/* 상단 내비게이션 바: 수평 정렬 및 고정 */ +.nav-header { + display: flex; + align-items: center; /* 수직 중앙 정렬 */ + padding: 16px 20px; + background-color: #ffffff; + position: sticky; + top: 0; + z-index: 100; + border-bottom: 1px solid #f1f3f5; +} + +#back-arrow { + text-decoration: none; + color: #333333; + font-size: 24px; + font-weight: 300; + margin-right: 12px; /* 타이틀과의 간격 확보 */ + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; +} + +.header-title { + font-size: 18px; + font-weight: 700; + color: #212529; + margin: 0; + letter-spacing: -0.5px; +} + +/* 본문 콘텐츠 영역 */ +.content { + padding: 24px 20px; + flex: 1; +} + +.intro-text { + font-size: 15px; + color: #495057; + line-height: 1.6; + margin-bottom: 24px; +} + +/* 요약 박스: 강조 포인트 */ +.summary-box { + background-color: #f1f7ff; /* 연세 블루 톤 가미 */ + border-radius: 12px; + padding: 18px; + margin-bottom: 32px; +} + +.summary-title { + font-weight: 700; + font-size: 15px; + color: #004b95; /* 포인트 컬러 */ + margin-bottom: 12px; + display: flex; + align-items: center; +} + +.summary-box ul { + margin: 0; + padding-left: 18px; +} + +.summary-box li { + font-size: 14px; + color: #343a40; + margin-bottom: 8px; +} + +/* 상세 섹션 */ +.detail-section h2 { + font-size: 16px; + font-weight: 700; + color: #212529; + margin: 28px 0 12px 0; + border-left: 4px solid #004b95; /* 섹션 구분선 */ + padding-left: 10px; +} + +.detail-section p, +.detail-section li { + font-size: 14px; + color: #6c757d; + line-height: 1.7; + margin: 8px 0; +} + +.detail-section ul { + padding-left: 18px; +} + +/* 푸터 영역 */ +footer { + padding: 40px 20px 30px; + text-align: center; + background-color: #f8f9fa; + border-top: 1px solid #f1f3f5; +} + +footer p { + font-size: 12px; + color: #adb5bd; + margin: 4px 0; +} \ No newline at end of file diff --git a/backend-core/static/users/css/signup.css b/backend-core/static/users/css/signup.css index 1c4335e..661d043 100644 --- a/backend-core/static/users/css/signup.css +++ b/backend-core/static/users/css/signup.css @@ -100,6 +100,14 @@ input { box-sizing: border-box; } +#password_condition { + font-size: 12px; + color: #667085; + margin-top: 8px; + margin-bottom: 0; + line-height: 1.5; +} + /* 1. 프로필 이미지 업로드 섹션 (새로 추가) */ .profile-upload-group { display: flex; @@ -314,8 +322,7 @@ input { } /* 8. 이용약관: 하단으로 충분히 공간 띄움 */ -.signup-container::after { - content: "회원가입 시 Uniquest의 이용약관 및 개인정보처리방침에 동의하는 것으로 간주됩니다."; +#announcement { display: block; margin-top: 30px; padding: 16px; @@ -325,4 +332,8 @@ input { color: #667085; line-height: 1.6; text-align: left; +} + +#announcement a{ + text-decoration: underline; } \ No newline at end of file diff --git a/backend-core/static/users/js/review_page.js b/backend-core/static/users/js/review_page.js index 52f7f56..1e0b0fb 100644 --- a/backend-core/static/users/js/review_page.js +++ b/backend-core/static/users/js/review_page.js @@ -141,7 +141,7 @@ async function send_info(){ if (response.ok){ const data = await response.json() - console.log(data) + window.location.href = `/api/users/homepage/` } else { console.log('실패') } diff --git a/backend-core/staticfiles/chat/js/room.js b/backend-core/staticfiles/chat/js/room.js index 3cd13e9..743001f 100644 --- a/backend-core/staticfiles/chat/js/room.js +++ b/backend-core/staticfiles/chat/js/room.js @@ -99,6 +99,11 @@ class ChatClient { acceptBtn.addEventListener('click', () => this.acceptMission()); } + const reviewBtn = document.getElementById('reviewPageBtn'); + if(reviewBtn) { + reviewBtn.addEventListener('click',() => window.location.href = `/api/users/review_page/${this.missionId}/`) + } + // 메시지 전송 if (this.sendBtn) { this.sendBtn.addEventListener('click', () => this.sendMessage()); @@ -237,6 +242,7 @@ class ChatClient { if (res.ok && data.success) { alert(data.message || '미션이 완료되었습니다.'); if (btn) btn.remove(); + window.location.href = `/api/users/review_page/${this.missionId}/` } else { alert(data.error || '미션 완료에 실패했습니다.'); btn.disabled = false; diff --git a/backend-core/staticfiles/users/css/announcement.css b/backend-core/staticfiles/users/css/announcement.css new file mode 100644 index 0000000..46ace1f --- /dev/null +++ b/backend-core/staticfiles/users/css/announcement.css @@ -0,0 +1,130 @@ +/* 가로 393px 고정 및 모바일 인터페이스 최적화 */ +body { + margin: 0; + padding: 0; + background-color: #f8f9fa; /* 외부 배경은 차분하게 */ + display: flex; + justify-content: center; + font-family: 'Pretendard', -apple-system, BlinkMacSystemFont, system-ui, Roboto, sans-serif; + -webkit-font-smoothing: antialiased; +} + +.container { + width: 393px; /* 팀장님의 물리적 규격 준수 */ + min-height: 100vh; + background-color: #ffffff; + box-sizing: border-box; + display: flex; + flex-direction: column; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.05); +} + +/* 상단 내비게이션 바: 수평 정렬 및 고정 */ +.nav-header { + display: flex; + align-items: center; /* 수직 중앙 정렬 */ + padding: 16px 20px; + background-color: #ffffff; + position: sticky; + top: 0; + z-index: 100; + border-bottom: 1px solid #f1f3f5; +} + +#back-arrow { + text-decoration: none; + color: #333333; + font-size: 24px; + font-weight: 300; + margin-right: 12px; /* 타이틀과의 간격 확보 */ + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; +} + +.header-title { + font-size: 18px; + font-weight: 700; + color: #212529; + margin: 0; + letter-spacing: -0.5px; +} + +/* 본문 콘텐츠 영역 */ +.content { + padding: 24px 20px; + flex: 1; +} + +.intro-text { + font-size: 15px; + color: #495057; + line-height: 1.6; + margin-bottom: 24px; +} + +/* 요약 박스: 강조 포인트 */ +.summary-box { + background-color: #f1f7ff; /* 연세 블루 톤 가미 */ + border-radius: 12px; + padding: 18px; + margin-bottom: 32px; +} + +.summary-title { + font-weight: 700; + font-size: 15px; + color: #004b95; /* 포인트 컬러 */ + margin-bottom: 12px; + display: flex; + align-items: center; +} + +.summary-box ul { + margin: 0; + padding-left: 18px; +} + +.summary-box li { + font-size: 14px; + color: #343a40; + margin-bottom: 8px; +} + +/* 상세 섹션 */ +.detail-section h2 { + font-size: 16px; + font-weight: 700; + color: #212529; + margin: 28px 0 12px 0; + border-left: 4px solid #004b95; /* 섹션 구분선 */ + padding-left: 10px; +} + +.detail-section p, +.detail-section li { + font-size: 14px; + color: #6c757d; + line-height: 1.7; + margin: 8px 0; +} + +.detail-section ul { + padding-left: 18px; +} + +/* 푸터 영역 */ +footer { + padding: 40px 20px 30px; + text-align: center; + background-color: #f8f9fa; + border-top: 1px solid #f1f3f5; +} + +footer p { + font-size: 12px; + color: #adb5bd; + margin: 4px 0; +} \ No newline at end of file diff --git a/backend-core/staticfiles/users/css/signup.css b/backend-core/staticfiles/users/css/signup.css index 1c4335e..661d043 100644 --- a/backend-core/staticfiles/users/css/signup.css +++ b/backend-core/staticfiles/users/css/signup.css @@ -100,6 +100,14 @@ input { box-sizing: border-box; } +#password_condition { + font-size: 12px; + color: #667085; + margin-top: 8px; + margin-bottom: 0; + line-height: 1.5; +} + /* 1. 프로필 이미지 업로드 섹션 (새로 추가) */ .profile-upload-group { display: flex; @@ -314,8 +322,7 @@ input { } /* 8. 이용약관: 하단으로 충분히 공간 띄움 */ -.signup-container::after { - content: "회원가입 시 Uniquest의 이용약관 및 개인정보처리방침에 동의하는 것으로 간주됩니다."; +#announcement { display: block; margin-top: 30px; padding: 16px; @@ -325,4 +332,8 @@ input { color: #667085; line-height: 1.6; text-align: left; +} + +#announcement a{ + text-decoration: underline; } \ No newline at end of file diff --git a/backend-core/staticfiles/users/js/review_page.js b/backend-core/staticfiles/users/js/review_page.js index 52f7f56..1e0b0fb 100644 --- a/backend-core/staticfiles/users/js/review_page.js +++ b/backend-core/staticfiles/users/js/review_page.js @@ -141,7 +141,7 @@ async function send_info(){ if (response.ok){ const data = await response.json() - console.log(data) + window.location.href = `/api/users/homepage/` } else { console.log('실패') } diff --git a/backend-core/templates/chat/room.html b/backend-core/templates/chat/room.html index 8e23b5a..4c1cff8 100644 --- a/backend-core/templates/chat/room.html +++ b/backend-core/templates/chat/room.html @@ -62,6 +62,9 @@

{{ other_user.username|default:mission.author.username {% if can_accept %} {% endif %} + {% if mission_end %} + + {% endif %} {% endif %} diff --git a/backend-core/templates/missions/mission_detail.html b/backend-core/templates/missions/mission_detail.html index fc8343f..b2fd402 100644 --- a/backend-core/templates/missions/mission_detail.html +++ b/backend-core/templates/missions/mission_detail.html @@ -212,7 +212,7 @@
- +

미션 상세

diff --git a/backend-core/templates/users/announcement_page.html b/backend-core/templates/users/announcement_page.html new file mode 100644 index 0000000..485c2f5 --- /dev/null +++ b/backend-core/templates/users/announcement_page.html @@ -0,0 +1,49 @@ +{%load static%} + + + + + + 개인정보 처리방침 | Uniquest + + + +
+ + +
+

유니퀘스트는 이용자의 소중한 개인정보를 안전하게 관리합니다.

+ +
+

💡 수집 정보 요약

+
    +
  • 학교 이메일: 연세인 인증용 (필수)
  • +
  • 이름(닉네임): 자유로운 임의 입력
  • +
+
+ +
+

1. 수집 항목 및 방법

+
    +
  • 항목: 학교 메일(@yonsei.ac.kr), 닉네임
  • +
  • 방법: 회원가입 시 이용자 직접 입력
  • +
+ +

2. 이용 목적

+

본인 확인, 미션 매칭 식별, 서비스 고지사항 전달을 위해 사용합니다.

+ +

3. 보유 및 파기

+

회원 탈퇴 시 즉시 파기를 원칙으로 하여 개인정보의 엔트로피를 최소화합니다.

+
+
+ +
+

© 2026 Uniquest. All rights reserved.

+

시행 일자: 2026. 02. 15

+
+
+ + \ No newline at end of file diff --git a/backend-core/templates/users/review.html b/backend-core/templates/users/review.html index 320f6c5..1b8a13b 100644 --- a/backend-core/templates/users/review.html +++ b/backend-core/templates/users/review.html @@ -4,6 +4,7 @@

평가

+
diff --git a/backend-core/templates/users/signup.html b/backend-core/templates/users/signup.html index f179b68..457fbc1 100644 --- a/backend-core/templates/users/signup.html +++ b/backend-core/templates/users/signup.html @@ -33,6 +33,7 @@

회원가입

+

비밀번호는 영문자, 특수기호로 이루어진 8자 이상의 비밀번호여야 합니다.

@@ -79,6 +80,12 @@

회원가입

이미 계정이 있으신가요?

로그인
+
+

+ 회원가입 시 Uniquest의 이용약관 및 개인정보처리방침에 동의하는 것으로 간주됩니다. + 상세보기 +

+
{% endblock %} diff --git a/backend-core/users/migrations/0008_alter_user_univ_email_alter_user_username.py b/backend-core/users/migrations/0008_alter_user_univ_email_alter_user_username.py new file mode 100644 index 0000000..6f4e856 --- /dev/null +++ b/backend-core/users/migrations/0008_alter_user_univ_email_alter_user_username.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.28 on 2026-02-15 13:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0007_alter_user_userphoto'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='univ_email', + field=models.EmailField(blank=True, max_length=254, null=True, unique=True, verbose_name='학교 이메일'), + ), + migrations.AlterField( + model_name='user', + name='username', + field=models.CharField(blank=True, max_length=150, null=True), + ), + ] diff --git a/backend-core/users/models.py b/backend-core/users/models.py index 22329fc..5a9dc59 100644 --- a/backend-core/users/models.py +++ b/backend-core/users/models.py @@ -15,6 +15,12 @@ class User(AbstractUser): """ 사용자 모델 (AbstractUser 상속) """ + username = models.CharField( + unique=False, + max_length=150, + blank=True, + null=True + ) # 대학 정보 (FK) university = models.ForeignKey( University, @@ -27,7 +33,15 @@ class User(AbstractUser): # 인증 및 신뢰도 is_student_verified = models.BooleanField(default=False, verbose_name="학생 인증 여부") - univ_email = models.EmailField(blank=True, null=True, verbose_name="학교 이메일") + univ_email = models.EmailField(blank=True, + null=True, + verbose_name="학교 이메일", + unique=True + ) + + USERNAME_FIELD = 'univ_email' + + REQUIRED_FIELDS = ['username'] # 매너 온도 (기본 80도) manner_score = models.FloatField(default=80, verbose_name="매너 온도") diff --git a/backend-core/users/urls.py b/backend-core/users/urls.py index e9e0bfe..2596631 100644 --- a/backend-core/users/urls.py +++ b/backend-core/users/urls.py @@ -10,7 +10,8 @@ # 1. 회원가입 (HTML 페이지 연결 삭제 -> API 연결) path('signup/', views.signup_page,name='signup_view'), path('signup/submit/', RegisterView.as_view(), name='signup'), - + path('signup/announcement/', views.announcement_page, name='announcement'), + # 2. 이메일 인증 path('verify-email/', views.verify_email, name='verify-email'), diff --git a/backend-core/users/views.py b/backend-core/users/views.py index 6cb7e8a..a09813e 100644 --- a/backend-core/users/views.py +++ b/backend-core/users/views.py @@ -194,6 +194,9 @@ def get(self, request): def login_page(request): return render(request, 'users/login.html') +#개인정보 수집 페이지 +def announcement_page(request): + return render(request,'users/announcement_page.html') @method_decorator(csrf_exempt, name='dispatch') class MyLoginView(APIView): @@ -458,10 +461,9 @@ def change_password(request): try: target_user = User.objects.get(univ_email=email) target_user.set_password(password) - print(target_user.password) target_user.save() - cache.delete("reset_token_{reset_token}") + cache.delete(f"reset_token_{reset_token}") return Response({"message": f"{target_user.username} 비밀번호가 성공적으로 변경되었습니다."}, status=200) except User.DoesNotExist: @@ -478,9 +480,12 @@ def render_review_page(request,mission_id): @api_view(['GET']) @permission_classes([IsAuthenticated]) def render_review_page_info(request,mission_id): - target_mission = Mission.objects.get(id=mission_id) - target_user = target_mission.author user = request.user + target_mission = Mission.objects.get(id=mission_id) + if (target_mission.author.username == user.username): # 내가 등록자 일 때 + target_user = target_mission.helper + else: + target_user = target_mission.author mission_name = target_mission.title username = target_user.username @@ -491,9 +496,13 @@ def render_review_page_info(request,mission_id): @api_view(['POST']) @permission_classes([IsAuthenticated]) def review_json(request): + user = request.user review_json = json.loads(request.body) target_mission = Mission.objects.get(id=review_json['personal_key']) - target_user = target_mission.author + if (target_mission.author.username == user.username): # 내가 등록자 일 때 + target_user = target_mission.helper + else: + target_user = target_mission.author target_user.review_datas.append(review_json) target_user.save() return Response({"status": "success", "message": target_user.username}, status=200)