fix(profile): 프로필 소셜 URL 필드를 신규 명세로 정리한다
This commit is contained in:
@@ -1149,8 +1149,9 @@ If you block this user, the following features will be restricted.
|
|||||||
// 라벨
|
// 라벨
|
||||||
static var instagram: String { pick(ko: "인스타그램", en: "Instagram", ja: "Instagram") }
|
static var instagram: String { pick(ko: "인스타그램", en: "Instagram", ja: "Instagram") }
|
||||||
static var youtube: String { pick(ko: "유튜브", en: "YouTube", ja: "YouTube") }
|
static var youtube: String { pick(ko: "유튜브", en: "YouTube", ja: "YouTube") }
|
||||||
static var website: String { pick(ko: "웹사이트", en: "Website", ja: "ウェブサイト") }
|
static var kakaoOpenChat: String { pick(ko: "오픈채팅", en: "Open Chat", ja: "オープンチャット") }
|
||||||
static var blog: String { pick(ko: "블로그", en: "Blog", ja: "ブログ") }
|
static var fancimm: String { pick(ko: "팬심M", en: "Fancimm", ja: "Fancimm") }
|
||||||
|
static var x: String { pick(ko: "X", en: "X", ja: "X") }
|
||||||
|
|
||||||
// 플레이스홀더
|
// 플레이스홀더
|
||||||
static var instagramUrlPlaceholder: String {
|
static var instagramUrlPlaceholder: String {
|
||||||
@@ -1159,11 +1160,14 @@ If you block this user, the following features will be restricted.
|
|||||||
static var youtubeUrlPlaceholder: String {
|
static var youtubeUrlPlaceholder: String {
|
||||||
pick(ko: "유튜브 URL", en: "YouTube URL", ja: "YouTubeのURL")
|
pick(ko: "유튜브 URL", en: "YouTube URL", ja: "YouTubeのURL")
|
||||||
}
|
}
|
||||||
static var websiteUrlPlaceholder: String {
|
static var kakaoOpenChatUrlPlaceholder: String {
|
||||||
pick(ko: "웹사이트 URL", en: "Website URL", ja: "ウェブサイト URL")
|
pick(ko: "오픈채팅 URL", en: "Open Chat URL", ja: "オープンチャット URL")
|
||||||
}
|
}
|
||||||
static var blogUrlPlaceholder: String {
|
static var fancimmUrlPlaceholder: String {
|
||||||
pick(ko: "블로그 URL", en: "Blog URL", ja: "ブログ URL")
|
pick(ko: "팬심M URL", en: "Fancimm URL", ja: "Fancimm URL")
|
||||||
|
}
|
||||||
|
static var xUrlPlaceholder: String {
|
||||||
|
pick(ko: "X URL", en: "X URL", ja: "X URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 소개글 입력 플레이스홀더
|
// 소개글 입력 플레이스홀더
|
||||||
|
|||||||
@@ -17,8 +17,10 @@ struct GetProfileResponse: Decodable {
|
|||||||
let rewardCan: Int
|
let rewardCan: Int
|
||||||
let youtubeUrl: String?
|
let youtubeUrl: String?
|
||||||
let instagramUrl: String?
|
let instagramUrl: String?
|
||||||
let blogUrl: String?
|
let fancimmUrl: String?
|
||||||
let websiteUrl: String?
|
let xUrl: String?
|
||||||
|
let xurl: String?
|
||||||
|
let kakaoOpenChatUrl: String?
|
||||||
let introduce: String
|
let introduce: String
|
||||||
let tags: [String]
|
let tags: [String]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ struct ProfileUpdateRequest: Encodable {
|
|||||||
var introduce: String? = nil
|
var introduce: String? = nil
|
||||||
var youtubeUrl: String? = nil
|
var youtubeUrl: String? = nil
|
||||||
var instagramUrl: String? = nil
|
var instagramUrl: String? = nil
|
||||||
var websiteUrl: String? = nil
|
var fancimmUrl: String? = nil
|
||||||
var blogUrl: String? = nil
|
var xUrl: String? = nil
|
||||||
|
var kakaoOpenChatUrl: String? = nil
|
||||||
var isVisibleDonationRank: Bool? = nil
|
var isVisibleDonationRank: Bool? = nil
|
||||||
var donationRankingPeriod: DonationRankingPeriod? = nil
|
var donationRankingPeriod: DonationRankingPeriod? = nil
|
||||||
let container: String = "ios"
|
let container: String = "ios"
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ struct ProfileUpdateView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func InstagramAndYoutubeAccountView() -> some View {
|
func SocialAccountUrlView() -> some View {
|
||||||
VStack(spacing: 16.7) {
|
VStack(spacing: 16.7) {
|
||||||
UserTextField(
|
UserTextField(
|
||||||
title: I18n.ProfileUpdate.instagram,
|
title: I18n.ProfileUpdate.instagram,
|
||||||
@@ -185,17 +185,24 @@ struct ProfileUpdateView: View {
|
|||||||
)
|
)
|
||||||
|
|
||||||
UserTextField(
|
UserTextField(
|
||||||
title: I18n.ProfileUpdate.website,
|
title: I18n.ProfileUpdate.kakaoOpenChat,
|
||||||
hint: I18n.ProfileUpdate.websiteUrlPlaceholder,
|
hint: I18n.ProfileUpdate.kakaoOpenChatUrlPlaceholder,
|
||||||
isSecure: false,
|
isSecure: false,
|
||||||
variable: $viewModel.websiteUrl
|
variable: $viewModel.kakaoOpenChatUrl
|
||||||
)
|
)
|
||||||
|
|
||||||
UserTextField(
|
UserTextField(
|
||||||
title: I18n.ProfileUpdate.blog,
|
title: I18n.ProfileUpdate.fancimm,
|
||||||
hint: I18n.ProfileUpdate.blogUrlPlaceholder,
|
hint: I18n.ProfileUpdate.fancimmUrlPlaceholder,
|
||||||
isSecure: false,
|
isSecure: false,
|
||||||
variable: $viewModel.blogUrl
|
variable: $viewModel.fancimmUrl
|
||||||
|
)
|
||||||
|
|
||||||
|
UserTextField(
|
||||||
|
title: I18n.ProfileUpdate.x,
|
||||||
|
hint: I18n.ProfileUpdate.xUrlPlaceholder,
|
||||||
|
isSecure: false,
|
||||||
|
variable: $viewModel.xUrl
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.padding(.vertical, 20)
|
.padding(.vertical, 20)
|
||||||
@@ -331,7 +338,7 @@ struct ProfileUpdateView: View {
|
|||||||
.padding(.top, 13.3)
|
.padding(.top, 13.3)
|
||||||
|
|
||||||
if UserDefaults.string(forKey: .role) == MemberRole.CREATOR.rawValue {
|
if UserDefaults.string(forKey: .role) == MemberRole.CREATOR.rawValue {
|
||||||
InstagramAndYoutubeAccountView()
|
SocialAccountUrlView()
|
||||||
.padding(.top, 13.3)
|
.padding(.top, 13.3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,9 @@ final class ProfileUpdateViewModel: ObservableObject {
|
|||||||
@Published var nickname = ""
|
@Published var nickname = ""
|
||||||
@Published var youtubeUrl = ""
|
@Published var youtubeUrl = ""
|
||||||
@Published var instagramUrl = ""
|
@Published var instagramUrl = ""
|
||||||
@Published var websiteUrl = ""
|
@Published var kakaoOpenChatUrl = ""
|
||||||
@Published var blogUrl = ""
|
@Published var fancimmUrl = ""
|
||||||
|
@Published var xUrl = ""
|
||||||
@Published var gender: Gender = .NONE
|
@Published var gender: Gender = .NONE
|
||||||
@Published var introduce = ""
|
@Published var introduce = ""
|
||||||
|
|
||||||
@@ -75,8 +76,9 @@ final class ProfileUpdateViewModel: ObservableObject {
|
|||||||
self.nickname = data.nickname
|
self.nickname = data.nickname
|
||||||
self.youtubeUrl = data.youtubeUrl ?? ""
|
self.youtubeUrl = data.youtubeUrl ?? ""
|
||||||
self.instagramUrl = data.instagramUrl ?? ""
|
self.instagramUrl = data.instagramUrl ?? ""
|
||||||
self.blogUrl = data.blogUrl ?? ""
|
self.kakaoOpenChatUrl = data.kakaoOpenChatUrl ?? ""
|
||||||
self.websiteUrl = data.websiteUrl ?? ""
|
self.fancimmUrl = data.fancimmUrl ?? ""
|
||||||
|
self.xUrl = preferredXUrl(xUrl: data.xUrl, xurl: data.xurl)
|
||||||
self.introduce = data.introduce
|
self.introduce = data.introduce
|
||||||
self.gender = data.gender
|
self.gender = data.gender
|
||||||
self.tags.append(contentsOf: data.tags)
|
self.tags.append(contentsOf: data.tags)
|
||||||
@@ -100,11 +102,18 @@ final class ProfileUpdateViewModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateProfile() {
|
func updateProfile() {
|
||||||
|
let originalYouTubeUrl = profileResponse!.youtubeUrl ?? ""
|
||||||
|
let originalInstagramUrl = profileResponse!.instagramUrl ?? ""
|
||||||
|
let originalKakaoOpenChatUrl = profileResponse!.kakaoOpenChatUrl ?? ""
|
||||||
|
let originalFancimmUrl = profileResponse!.fancimmUrl ?? ""
|
||||||
|
let originalXUrl = preferredXUrl(xUrl: profileResponse!.xUrl, xurl: profileResponse!.xurl)
|
||||||
|
|
||||||
if profileResponse!.nickname != nickname ||
|
if profileResponse!.nickname != nickname ||
|
||||||
profileResponse!.youtubeUrl != youtubeUrl ||
|
originalYouTubeUrl != youtubeUrl ||
|
||||||
profileResponse!.instagramUrl != instagramUrl ||
|
originalInstagramUrl != instagramUrl ||
|
||||||
profileResponse!.blogUrl != blogUrl ||
|
originalKakaoOpenChatUrl != kakaoOpenChatUrl ||
|
||||||
profileResponse!.websiteUrl != websiteUrl ||
|
originalFancimmUrl != fancimmUrl ||
|
||||||
|
originalXUrl != xUrl ||
|
||||||
profileResponse!.gender != gender ||
|
profileResponse!.gender != gender ||
|
||||||
profileResponse!.introduce != introduce ||
|
profileResponse!.introduce != introduce ||
|
||||||
!insertTags.isEmpty ||
|
!insertTags.isEmpty ||
|
||||||
@@ -115,10 +124,11 @@ final class ProfileUpdateViewModel: ObservableObject {
|
|||||||
nickname: profileResponse!.nickname != nickname ? nickname : nil,
|
nickname: profileResponse!.nickname != nickname ? nickname : nil,
|
||||||
gender: profileResponse!.gender != gender ? gender : nil,
|
gender: profileResponse!.gender != gender ? gender : nil,
|
||||||
introduce: profileResponse!.introduce != introduce && introduce.trimmingCharacters(in: .whitespacesAndNewlines) != placeholder ? introduce : nil,
|
introduce: profileResponse!.introduce != introduce && introduce.trimmingCharacters(in: .whitespacesAndNewlines) != placeholder ? introduce : nil,
|
||||||
youtubeUrl: profileResponse!.youtubeUrl != youtubeUrl ? youtubeUrl : nil,
|
youtubeUrl: originalYouTubeUrl != youtubeUrl ? youtubeUrl : nil,
|
||||||
instagramUrl: profileResponse!.instagramUrl != instagramUrl ? instagramUrl : nil,
|
instagramUrl: originalInstagramUrl != instagramUrl ? instagramUrl : nil,
|
||||||
websiteUrl: profileResponse!.websiteUrl != websiteUrl ? websiteUrl : nil,
|
fancimmUrl: originalFancimmUrl != fancimmUrl ? fancimmUrl : nil,
|
||||||
blogUrl: profileResponse!.blogUrl != blogUrl ? blogUrl : nil,
|
xUrl: originalXUrl != xUrl ? xUrl : nil,
|
||||||
|
kakaoOpenChatUrl: originalKakaoOpenChatUrl != kakaoOpenChatUrl ? kakaoOpenChatUrl : nil,
|
||||||
insertTags: !insertTags.isEmpty ? insertTags : nil,
|
insertTags: !insertTags.isEmpty ? insertTags : nil,
|
||||||
removeTags: !removeTags.isEmpty ? removeTags : nil
|
removeTags: !removeTags.isEmpty ? removeTags : nil
|
||||||
)
|
)
|
||||||
@@ -327,4 +337,18 @@ final class ProfileUpdateViewModel: ObservableObject {
|
|||||||
let predicate = NSPredicate(format:"SELF MATCHES %@", passwordRegEx)
|
let predicate = NSPredicate(format:"SELF MATCHES %@", passwordRegEx)
|
||||||
return predicate.evaluate(with: newPassword)
|
return predicate.evaluate(with: newPassword)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func preferredXUrl(xUrl: String?, xurl: String?) -> String {
|
||||||
|
if let xUrl,
|
||||||
|
!xUrl.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||||
|
return xUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
if let xurl,
|
||||||
|
!xurl.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||||
|
return xurl
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
89
docs/20260227_프로필소셜URL필드변경.md
Normal file
89
docs/20260227_프로필소셜URL필드변경.md
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
# 20260227 프로필 소셜 URL 필드 변경
|
||||||
|
|
||||||
|
## 구현 목표
|
||||||
|
- 프로필 수정 화면에서 소셜 URL 입력 항목을 `인스타그램`, `유튜브`, `카카오 오픈채팅`, `팬심M`, `X`로 변경한다.
|
||||||
|
- 기존 함수/프로퍼티 네이밍을 신규 요구사항에 맞게 정리한다.
|
||||||
|
- `ProfileUpdateRequest`의 URL 프로퍼티를 아래 명세로 변경한다.
|
||||||
|
- `youtubeUrl`
|
||||||
|
- `instagramUrl`
|
||||||
|
- `fancimmUrl`
|
||||||
|
- `xUrl`
|
||||||
|
- `kakaoOpenChatUrl`
|
||||||
|
- 사용자 노출 문자열을 `I18n` 기반으로 국제화 처리한다.
|
||||||
|
|
||||||
|
## 체크리스트
|
||||||
|
- [x] 기존 프로필 수정 소셜 URL 데이터 흐름(View/ViewModel/Request/I18n) 점검
|
||||||
|
- [x] `ProfileUpdateView` 소셜 입력 섹션 항목/함수명 변경
|
||||||
|
- [x] `ProfileUpdateViewModel` 소셜 URL 상태/비교/요청 매핑 변경
|
||||||
|
- [x] `ProfileUpdateRequest` URL 프로퍼티명을 신규 명세로 변경
|
||||||
|
- [x] `I18n.ProfileUpdate` 라벨/플레이스홀더 키를 신규 항목 기준으로 변경
|
||||||
|
- [x] 수정 파일 LSP 진단 확인
|
||||||
|
- [x] 빌드/테스트 실행 및 결과 확인
|
||||||
|
|
||||||
|
## 검증 기록
|
||||||
|
- 2026-02-27
|
||||||
|
- 무엇: 프로필 수정 소셜 URL 입력 항목을 `인스타그램/유튜브/카카오 오픈채팅/팬심M/X`로 변경하고, 요청 프로퍼티를 `youtubeUrl/instagramUrl/fancimmUrl/xUrl/kakaoOpenChatUrl`로 정렬했다.
|
||||||
|
- 왜: 크리에이터 프로필 입력 요구사항 변경 및 서버 요청 스키마 변경 요구를 반영하기 위해.
|
||||||
|
- 어떻게:
|
||||||
|
- `lsp_diagnostics` 실행 대상
|
||||||
|
- `SodaLive/Sources/MyPage/Profile/ProfileUpdateView.swift`
|
||||||
|
- `SodaLive/Sources/MyPage/Profile/ProfileUpdateViewModel.swift`
|
||||||
|
- `SodaLive/Sources/MyPage/Profile/ProfileUpdateRequest.swift`
|
||||||
|
- `SodaLive/Sources/MyPage/Profile/GetProfileResponse.swift`
|
||||||
|
- `SodaLive/Sources/I18n/I18n.swift`
|
||||||
|
- 빌드
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build`
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`
|
||||||
|
- 테스트
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test`
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test`
|
||||||
|
- 결과:
|
||||||
|
- 빌드: `SodaLive`, `SodaLive-dev` 모두 `** BUILD SUCCEEDED **` 확인.
|
||||||
|
- 테스트: 두 스킴 모두 `Scheme ... is not currently configured for the test action.`로 실행 불가(프로젝트 설정 이슈).
|
||||||
|
- LSP: SourceKit 환경에서 모듈/심볼 해석 실패(`No such module 'Kingfisher'`, `No such module 'UIKit'`, `LanguageHeaderProvider` 범위 미해결) 경고가 있었으나, 실제 Xcode 빌드는 성공해 코드 변경 자체의 컴파일은 통과.
|
||||||
|
|
||||||
|
- 2026-02-27 (리뷰 피드백 반영)
|
||||||
|
- 무엇: `xUrl` 마이그레이션 호환(`xUrl`+`xurl` 동시 전송)과 `xUrl/xurl` first non-empty 폴백을 추가 보완했다.
|
||||||
|
- 왜: 구/신 키가 혼재된 서버 환경에서 X URL 저장 누락 및 빈 문자열 우선 선택 이슈를 방지하기 위해.
|
||||||
|
- 어떻게:
|
||||||
|
- `ProfileUpdateRequest.encode(to:)` 커스텀 인코딩으로 `xUrl`, `xurl` 동시 직렬화
|
||||||
|
- `ProfileUpdateViewModel`에 `preferredXUrl` 헬퍼 적용
|
||||||
|
- `GetProfileResponse`/`ProfileUpdateViewModel`/`ProfileUpdateRequest`/`ProfileUpdateView`의 유튜브 키를 `youtubeUrl`로 정리
|
||||||
|
- 빌드
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build`
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`
|
||||||
|
- 테스트
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test`
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test`
|
||||||
|
- 결과:
|
||||||
|
- 빌드: 두 스킴 모두 `** BUILD SUCCEEDED **` 확인.
|
||||||
|
- 테스트: 두 스킴 모두 `Scheme ... is not currently configured for the test action.`로 실행 불가(동일).
|
||||||
|
|
||||||
|
- 2026-02-27 (사용자 요청 반영: youtubeUrl 원복 + X 폴백 명시화)
|
||||||
|
- 무엇: `ulwyoutubeUrl` 사용을 제거하고 전체 프로필 수정 흐름을 `youtubeUrl`로 원복했다. 동시에 X 값은 `xUrl`이 비어있지 않으면 우선, 아니면 `xurl`, 둘 다 없으면 `""`로 처리하도록 고정했다.
|
||||||
|
- 왜: 사용자 요청에 따라 비의도적인 `ulwyoutubeUrl` 도입을 제거하고, X 폴백 조건을 명확히 해 빈 문자열 우선 문제를 방지하기 위해.
|
||||||
|
- 어떻게:
|
||||||
|
- `ProfileUpdateViewModel`: `@Published`/초기화/비교/요청 매핑을 `youtubeUrl`로 변경
|
||||||
|
- `ProfileUpdateView`: 유튜브 입력 바인딩을 `$viewModel.youtubeUrl`로 변경
|
||||||
|
- `ProfileUpdateRequest`: `youtubeUrl` 프로퍼티 및 인코딩 키 정리
|
||||||
|
- `GetProfileResponse`: `ulwyoutubeUrl` 제거, `youtubeUrl`만 유지
|
||||||
|
- `preferredXUrl(xUrl:xurl:)`로 `xUrl -> xurl -> ""` 순서 폴백 구현
|
||||||
|
|
||||||
|
- 2026-02-27 (원복 이후 검증)
|
||||||
|
- 무엇: `youtubeUrl` 원복과 X 폴백(`xUrl` 비어있음 검사 후 `xurl`, 마지막 `""`) 적용 상태를 재검증했다.
|
||||||
|
- 어떻게:
|
||||||
|
- `lsp_diagnostics` 실행 대상
|
||||||
|
- `SodaLive/Sources/MyPage/Profile/ProfileUpdateView.swift`
|
||||||
|
- `SodaLive/Sources/MyPage/Profile/ProfileUpdateViewModel.swift`
|
||||||
|
- `SodaLive/Sources/MyPage/Profile/ProfileUpdateRequest.swift`
|
||||||
|
- `SodaLive/Sources/MyPage/Profile/GetProfileResponse.swift`
|
||||||
|
- 빌드
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build`
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`
|
||||||
|
- 테스트
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test`
|
||||||
|
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test`
|
||||||
|
- 결과:
|
||||||
|
- 빌드: 두 스킴 모두 `** BUILD SUCCEEDED **`.
|
||||||
|
- 테스트: 두 스킴 모두 `Scheme ... is not currently configured for the test action.`로 실행 불가.
|
||||||
|
- LSP: SourceKit 환경의 모듈/심볼 해석 제약(`UIKit`, `Kingfisher`, `Gender` 등)으로 로컬 진단 경고가 있으나, Xcode 실제 빌드 성공으로 변경 컴파일은 통과.
|
||||||
Reference in New Issue
Block a user