고객센터 페이지 추가

This commit is contained in:
Yu Sung 2023-08-11 07:33:38 +09:00
parent e082e07fa6
commit 64b0380671
13 changed files with 482 additions and 0 deletions

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "ic_service_center_kakao.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 B

View File

@ -51,4 +51,6 @@ enum AppStep {
case liveReservation case liveReservation
case liveReservationCancel(reservationId: Int) case liveReservationCancel(reservationId: Int)
case serviceCenter
} }

View File

@ -80,6 +80,9 @@ struct ContentView: View {
case .liveReservationCancel(let reservationId): case .liveReservationCancel(let reservationId):
LiveReservationCancelView(reservationId: reservationId) LiveReservationCancelView(reservationId: reservationId)
case .serviceCenter:
ServiceCenterView()
default: default:
EmptyView() EmptyView()
.frame(width: 0, height: 0, alignment: .topLeading) .frame(width: 0, height: 0, alignment: .topLeading)

View File

@ -0,0 +1,13 @@
//
// Faq.swift
// SodaLive
//
// Created by klaus on 2023/08/11.
//
import Foundation
struct Faq: Decodable, Hashable {
let question: String
let answer: String
}

View File

@ -0,0 +1,48 @@
//
// FaqApi.swift
// SodaLive
//
// Created by klaus on 2023/08/11.
//
import Foundation
import Moya
enum FaqApi {
case faqs(category: String)
case faqCategories
}
extension FaqApi: TargetType {
var baseURL: URL {
return URL(string: BASE_URL)!
}
var path: String {
switch self {
case .faqs:
return "/faq"
case .faqCategories:
return "/faq/category"
}
}
var method: Moya.Method {
return .get
}
var task: Task {
switch self {
case .faqCategories:
return .requestPlain
case .faqs(let category):
return .requestParameters(parameters: ["category" : category] as [String : Any], encoding: URLEncoding.queryString)
}
}
var headers: [String : String]? {
return ["Authorization": "Bearer \(UserDefaults.string(forKey: UserDefaultsKey.token))"]
}
}

View File

@ -0,0 +1,23 @@
//
// FaqRepository.swift
// SodaLive
//
// Created by klaus on 2023/08/11.
//
import Foundation
import CombineMoya
import Combine
import Moya
final class FaqRepository {
private let api = MoyaProvider<FaqApi>()
func getFaqs(category: String) -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher(.faqs(category: category))
}
func getFaqCategories() -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher(.faqCategories)
}
}

View File

@ -0,0 +1,85 @@
//
// FaqView.swift
// SodaLive
//
// Created by klaus on 2023/08/11.
//
import SwiftUI
import RichText
struct FaqView: View {
let faqs: [Faq]
@State private var openIndex = -1
var body: some View {
VStack(spacing: 0) {
ForEach(0..<faqs.count, id: \.self) { index in
let faq = faqs[index]
VStack(spacing: 0) {
HStack(alignment: .top, spacing: 6.7) {
Text("Q")
.font(.custom(Font.bold.rawValue, size: 13.3))
.foregroundColor(Color(hex: "9970ff"))
Text(faq.question)
.font(.custom(Font.medium.rawValue, size: 14.7))
.foregroundColor(Color(hex: "eeeeee"))
.fixedSize(horizontal: false, vertical: true)
Spacer()
Image(openIndex == index ? "btn_dropdown_up" : "btn_dropdown_down")
.resizable()
.frame(width: 20, height: 20)
}
.padding(.vertical, 20)
.padding(.horizontal, 6.7)
if openIndex == index {
HStack(alignment: .top, spacing: 6.7) {
Text("A")
.font(.custom(Font.bold.rawValue, size: 13.3))
.foregroundColor(Color(hex: "9970ff"))
.padding(.top, 13.3)
RichText(html: faq.answer)
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color(hex: "bbbbbb"))
}
.padding(.vertical, 20)
.padding(.horizontal, 6.7)
.background(Color(hex: "222222"))
}
}
.padding(.horizontal, 13.3)
.contentShape(Rectangle())
.onTapGesture {
if openIndex != index {
openIndex = index
} else {
openIndex = -1
}
}
}
}
.padding(.bottom, 40)
}
}
struct FaqView_Previews: PreviewProvider {
static var previews: some View {
FaqView(
faqs: [
Faq(question: "질문1", answer: "답변1"),
Faq(question: "질문2", answer: "답변2"),
Faq(question: "질문3", answer: "답변3"),
Faq(question: "질문4", answer: "답변4"),
Faq(question: "질문5", answer: "답변5")
]
)
}
}

View File

@ -29,6 +29,7 @@ struct ServiceCenterButtonView: View {
.background(Color(hex: "664aab")) .background(Color(hex: "664aab"))
.cornerRadius(6.7) .cornerRadius(6.7)
.onTapGesture { .onTapGesture {
AppState.shared.setAppStep(step: .serviceCenter)
} }
} }
} }

View File

@ -0,0 +1,31 @@
//
// ServiceCenterCategoryItemView.swift
// SodaLive
//
// Created by klaus on 2023/08/11.
//
import SwiftUI
struct ServiceCenterCategoryItemView: View {
let category: String
let isSelected: Bool
var body: some View {
GeometryReader { proxy in
Text(category)
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(.white)
.frame(width: proxy.size.width, height: 46.7)
.background(isSelected ? Color(hex: "9970ff") : Color(hex: "222222"))
.cornerRadius(4.7)
}
}
}
struct ServiceCenterCategoryItemView_Previews: PreviewProvider {
static var previews: some View {
ServiceCenterCategoryItemView(category: "전체", isSelected: false)
}
}

View File

@ -0,0 +1,48 @@
//
// ServiceCenterCategoryView.swift
// SodaLive
//
// Created by klaus on 2023/08/11.
//
import SwiftUI
struct ServiceCenterCategoryView: View {
let columns = [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible()),
]
let categories: [String]
@Binding var selectedCategory: String
var body: some View {
LazyVGrid(columns: columns, spacing: 10) {
ForEach(categories, id: \.self) { category in
ServiceCenterCategoryItemView(
category: category,
isSelected: selectedCategory == category
)
.frame(height: 46.7)
.onTapGesture {
if selectedCategory != category {
selectedCategory = category
}
}
}
}
.padding(.horizontal, 13.3)
}
}
struct ServiceCenterCategoryView_Previews: PreviewProvider {
static var previews: some View {
ServiceCenterCategoryView(
categories: ["전체", "사용방법", "수다", "결제/환불", "서비스/기타"],
selectedCategory: .constant("전체")
)
}
}

View File

@ -0,0 +1,99 @@
//
// ServiceCenterView.swift
// SodaLive
//
// Created by klaus on 2023/08/11.
//
import SwiftUI
struct ServiceCenterView: View {
@ObservedObject var viewModel = ServiceCenterViewModel()
var body: some View {
BaseView(isLoading: $viewModel.isLoading) {
VStack(spacing: 0) {
DetailNavigationBar(title: "고객센터")
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 0) {
Image("ic_logo")
.resizable()
.scaledToFill()
.frame(width: 106.7, height: 106.7, alignment: .top)
Text("고객센터")
.font(.custom(Font.bold.rawValue, size: 20))
.foregroundColor(Color(hex: "eeeeee"))
HStack(spacing: 13.3) {
Image("ic_service_center_kakao")
.resizable()
.scaledToFill()
.frame(width: 21, height: 18.8, alignment: .top)
Text("TALK 문의")
.font(.custom(Font.bold.rawValue, size: 13.3))
.foregroundColor(.black)
}
.padding(.vertical, 14)
.frame(width: screenSize().width - 26.7)
.background(Color(hex: "ffe368"))
.cornerRadius(8)
.padding(.top, 20)
.onTapGesture {
UIApplication.shared.open(URL(string: "http://pf.kakao.com/_sZaeb")!)
}
Rectangle()
.frame(width: screenSize().width, height: 6.7)
.foregroundColor(Color(hex: "232323"))
.padding(.vertical, 20)
Text("자주 묻는 질문")
.font(.custom(Font.bold.rawValue, size: 18.3))
.foregroundColor(Color(hex: "eeeeee"))
.frame(width: screenSize().width - 26.7, alignment: .leading)
ServiceCenterCategoryView(
categories: viewModel.categories,
selectedCategory: $viewModel.selectedCategory
)
.padding(.vertical, 20)
FaqView(faqs: viewModel.faqs)
}
}
}
}
.popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) {
GeometryReader { geo in
HStack {
Spacer()
Text(viewModel.errorMessage)
.padding(.vertical, 13.3)
.padding(.horizontal, 6.7)
.frame(width: geo.size.width - 66.7, alignment: .center)
.font(.custom(Font.medium.rawValue, size: 12))
.background(Color(hex: "9970ff"))
.foregroundColor(Color.white)
.multilineTextAlignment(.leading)
.fixedSize(horizontal: false, vertical: true)
.cornerRadius(20)
.padding(.top, 66.7)
Spacer()
}
}
}
.onAppear {
viewModel.getFaqCategories()
}
}
}
struct ServiceCenterView_Previews: PreviewProvider {
static var previews: some View {
ServiceCenterView()
}
}

View File

@ -0,0 +1,108 @@
//
// ServiceCenterViewModel.swift
// SodaLive
//
// Created by klaus on 2023/08/11.
//
import Foundation
import Combine
final class ServiceCenterViewModel: ObservableObject {
private let repository = FaqRepository()
private var subscription = Set<AnyCancellable>()
@Published var errorMessage = ""
@Published var isShowPopup = false
@Published var isLoading = false
@Published var faqs = [Faq]()
@Published var categories = [String]()
@Published var selectedCategory = "" {
didSet {
getFaqs()
}
}
func getFaqCategories() {
categories.removeAll()
isLoading = true
repository.getFaqCategories()
.sink { result in
switch result {
case .finished:
DEBUG_LOG("finish")
case .failure(let error):
ERROR_LOG(error.localizedDescription)
}
} receiveValue: { response in
self.isLoading = false
let responseData = response.data
do {
let jsonDecoder = JSONDecoder()
let decoded = try jsonDecoder.decode(ApiResponse<[String]>.self, from: responseData)
if let data = decoded.data, decoded.success {
self.categories.append(contentsOf: data)
if !data.isEmpty {
self.selectedCategory = data[0]
}
} else {
if let message = decoded.message {
self.errorMessage = message
} else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
}
self.isShowPopup = true
}
} catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
self.isShowPopup = true
}
}
.store(in: &subscription)
}
func getFaqs() {
faqs.removeAll()
isLoading = true
repository.getFaqs(category: self.selectedCategory)
.sink { result in
switch result {
case .finished:
DEBUG_LOG("finish")
case .failure(let error):
ERROR_LOG(error.localizedDescription)
}
} receiveValue: { response in
self.isLoading = false
let responseData = response.data
do {
let jsonDecoder = JSONDecoder()
let decoded = try jsonDecoder.decode(ApiResponse<[Faq]>.self, from: responseData)
if let data = decoded.data, decoded.success {
self.faqs.append(contentsOf: data)
} else {
if let message = decoded.message {
self.errorMessage = message
} else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
}
self.isShowPopup = true
}
} catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
self.isShowPopup = true
}
}
.store(in: &subscription)
}
}