diff --git a/.github/auto_assign.yml b/.github/auto_assign.yml
index 549a69d..71a1396 100644
--- a/.github/auto_assign.yml
+++ b/.github/auto_assign.yml
@@ -7,6 +7,7 @@ addAssignees: author
reviewers:
- minneee
- Peter1119
+ - Roy-wonji
# A number of reviewers added to the pull request
# Set 0 to add all the reviewers (default: 0)
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Contents.json
index 73c0059..7f73912 100644
--- a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Contents.json
+++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Contents.json
@@ -2,5 +2,8 @@
"info" : {
"author" : "xcode",
"version" : 1
+ },
+ "properties" : {
+ "compression-type" : "automatic"
}
}
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamiIntroduce.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamiIntroduce.imageset/Contents.json
new file mode 100644
index 0000000..98b4695
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamiIntroduce.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "TeamiIntroduce.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamiIntroduce.imageset/TeamiIntroduce.png b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamiIntroduce.imageset/TeamiIntroduce.png
new file mode 100644
index 0000000..fffc4cb
Binary files /dev/null and b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/TeamiIntroduce.imageset/TeamiIntroduce.png differ
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceAccident.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceAccident.imageset/Contents.json
new file mode 100644
index 0000000..76bf3d6
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceAccident.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "TeamInfroduce_Accident.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceAccident.imageset/TeamInfroduce_Accident.png b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceAccident.imageset/TeamInfroduce_Accident.png
new file mode 100644
index 0000000..0d4767d
Binary files /dev/null and b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceAccident.imageset/TeamInfroduce_Accident.png differ
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceCircle.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceCircle.imageset/Contents.json
new file mode 100644
index 0000000..a67eb54
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceCircle.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "TeamInfroduce_Circle.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceCircle.imageset/TeamInfroduce_Circle.png b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceCircle.imageset/TeamInfroduce_Circle.png
new file mode 100644
index 0000000..3cb6fcf
Binary files /dev/null and b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceCircle.imageset/TeamInfroduce_Circle.png differ
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceHeart.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceHeart.imageset/Contents.json
new file mode 100644
index 0000000..c0f5096
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceHeart.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "TeamInfroduce_Heart.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceHeart.imageset/TeamInfroduce_Heart.png b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceHeart.imageset/TeamInfroduce_Heart.png
new file mode 100644
index 0000000..600341c
Binary files /dev/null and b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroduceHeart.imageset/TeamInfroduce_Heart.png differ
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroducePerson.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroducePerson.imageset/Contents.json
new file mode 100644
index 0000000..d3af2dd
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroducePerson.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "TeamInfroduce_Person.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroducePerson.imageset/TeamInfroduce_Person.png b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroducePerson.imageset/TeamInfroduce_Person.png
new file mode 100644
index 0000000..0635f4c
Binary files /dev/null and b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/Images/teamInfroducePerson.imageset/TeamInfroduce_Person.png differ
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/blog.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/blog.imageset/Contents.json
new file mode 100644
index 0000000..3e8c32f
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/blog.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "blog.svg",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/blog.imageset/blog.svg b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/blog.imageset/blog.svg
new file mode 100644
index 0000000..d3b5f50
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/blog.imageset/blog.svg
@@ -0,0 +1,5 @@
+
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/check.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/check.imageset/Contents.json
new file mode 100644
index 0000000..17203cc
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/check.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "check.svg",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/check.imageset/check.svg b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/check.imageset/check.svg
new file mode 100644
index 0000000..daadbb8
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/check.imageset/check.svg
@@ -0,0 +1,4 @@
+
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/glabal.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/glabal.imageset/Contents.json
new file mode 100644
index 0000000..64594cb
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/glabal.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "glabal.svg",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/glabal.imageset/glabal.svg b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/glabal.imageset/glabal.svg
new file mode 100644
index 0000000..a1cd791
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/glabal.imageset/glabal.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/leftArrow.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/leftArrow.imageset/Contents.json
new file mode 100644
index 0000000..2a1fcd6
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/leftArrow.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "leftArrow.svg",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/leftArrow.imageset/leftArrow.svg b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/leftArrow.imageset/leftArrow.svg
new file mode 100644
index 0000000..45f1991
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/leftArrow.imageset/leftArrow.svg
@@ -0,0 +1,9 @@
+
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/link.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/link.imageset/Contents.json
new file mode 100644
index 0000000..6060b67
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/link.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "link.svg",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/link.imageset/link.svg b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/link.imageset/link.svg
new file mode 100644
index 0000000..f15bf06
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/link.imageset/link.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/people.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/people.imageset/Contents.json
new file mode 100644
index 0000000..1cc8d72
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/people.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "people.svg",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/people.imageset/people.svg b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/people.imageset/people.svg
new file mode 100644
index 0000000..3b74325
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/people.imageset/people.svg
@@ -0,0 +1,6 @@
+
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/rightArrow.imageset/Contents.json b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/rightArrow.imageset/Contents.json
new file mode 100644
index 0000000..bfef48d
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/rightArrow.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "rightArrow.svg",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/rightArrow.imageset/rightArrow.svg b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/rightArrow.imageset/rightArrow.svg
new file mode 100644
index 0000000..894dae3
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Resources/Assets.xcassets/icon/rightArrow.imageset/rightArrow.svg
@@ -0,0 +1,3 @@
+
diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Color/Extension+ShapeStyle.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Color/Extension+ShapeStyle.swift
index 15cfe68..01e3bfa 100644
--- a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Color/Extension+ShapeStyle.swift
+++ b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Color/Extension+ShapeStyle.swift
@@ -13,12 +13,14 @@ extension ShapeStyle where Self == Color {
static var staticWhite: Color { .init(hex: "FFFFFF") }
static var staticBlack: Color { .init(hex: "0C0E0F") }
+ static var shadowColor: Color { .init(hex: "000000")}
// MARK: - Static Text
static var textPrimary: Color { .init(hex: "0A0A0A") }
static var textSecondary: Color { .init(hex: "717182") }
static var textSecondary100: Color { .init(hex: "525252") }
+ static var textGray100: Color { .init(hex: "7D7E8C") }
static var textInactive: Color { .init(hex: "70737C47").opacity(0.28) }
// MARK: - Static Background
@@ -47,6 +49,7 @@ extension ShapeStyle where Self == Color {
static var gray90: Color { .init(hex: "202325") }
static var grayError: Color { .init(hex: "FF5050") }
static var grayWhite: Color { .init(hex: "FFFFFF") }
+ static var blueGray: Color { .init(hex: "7A7A89") }
static var grayPrimary: Color { .init(hex: "0099FF") }
// MARK: - Surface
@@ -68,7 +71,7 @@ extension ShapeStyle where Self == Color {
// MARK: - NatureBlue
- static var blue10: Color { .init(hex: "F5F8FF") }
+ static var blue10: Color { .init(hex: "155DFC") }
static var blue20: Color { .init(hex: "E1EAFF") }
static var blue30: Color { .init(hex: "C1D3FF") }
static var blue40: Color { .init(hex: "0D82F9") }
@@ -99,6 +102,10 @@ extension ShapeStyle where Self == Color {
static var gray600: Color { .init(hex: "808080") }
static var gray800: Color { .init(hex: "4D4D4D") }
+
+
+ static var green: Color { .init(hex: "00A63E") }
+ static var lightPurple: Color { .init(hex: "9810FA") }
static var error: Color { .init(hex: "FF5050") }
static var basicBlue: Color { .init(hex: "0099FF") }
diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/CustomNavigationBackBar.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/CustomNavigationBackBar.swift
new file mode 100644
index 0000000..42f7c2a
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Componet/Navigation/CustomNavigationBackBar.swift
@@ -0,0 +1,49 @@
+//
+// CustomNavigationBackBar.swift
+// TeamIntroduce
+//
+// Created by Wonji Suh on 8/12/25.
+//
+
+import SwiftUI
+
+ struct CustomNavigationBackBar: View {
+ private var buttonAction: () -> Void = { }
+ private var text: String
+
+
+ init(
+ text: String = "",
+ buttonAction: @escaping () -> Void,
+ ) {
+ self.buttonAction = buttonAction
+ self.text = text
+ }
+
+ var body: some View {
+ HStack {
+ Image(asset: .leftArrow)
+ .resizable()
+ .scaledToFit()
+ .frame(width: 10, height: 20)
+ .foregroundStyle(.staticWhite)
+
+ Spacer()
+ .frame(width: 20)
+
+ if !text.isEmpty {
+ Text(text)
+ .pretendardFont(family: .regular, size: 14)
+ .foregroundStyle(.textPrimary)
+ }
+
+ Spacer()
+
+
+ }
+ .padding(.horizontal, 30)
+ .onTapGesture {
+ buttonAction()
+ }
+ }
+}
diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/Extension+Image.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/Extension+Image.swift
new file mode 100644
index 0000000..229a230
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/Extension+Image.swift
@@ -0,0 +1,36 @@
+//
+// Extension+Image.swift
+// TeamIntroduce
+//
+// Created by Wonji Suh on 8/11/25.
+//
+
+import SwiftUI
+
+ extension UIImage {
+ convenience init?(_ asset: ImageAsset) {
+ self.init(named: asset.rawValue, in: Bundle.main, with: nil)
+ }
+
+ convenience init?(assetName: String) {
+ self.init(named: assetName, in: Bundle.main, with: nil)
+ }
+}
+
+extension Image {
+ init(asset: ImageAsset) {
+ if let uiImage = UIImage(asset) {
+ self.init(uiImage: uiImage)
+ } else {
+ self = Image(systemName: "questionmark")
+ }
+ }
+
+ init(assetName: String) {
+ if let uiImage = UIImage(assetName: assetName) {
+ self.init(uiImage: uiImage)
+ } else {
+ self = Image(systemName: "questionmark")
+ }
+ }
+}
diff --git a/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift
new file mode 100644
index 0000000..628858e
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Sources/DesignSytstem/Image/ImageAsset.swift
@@ -0,0 +1,28 @@
+//
+// ImageAsset.swift
+// TeamIntroduce
+//
+// Created by Wonji Suh on 8/11/25.
+//
+
+import Foundation
+
+enum ImageAsset: String {
+ case people
+ case rightArrow
+ case blog
+ case check
+ case leftArrow
+ case glabal
+ case link
+ case arrowRight
+ case missionLogo
+ case teamAgreementLogo
+ case teamBlogLogo
+ case teamIntroductionLogo
+ case teamiIntroduce
+ case teamInfroduceAccident
+ case teamInfroduceHeart
+ case teamInfroduceCircle
+ case teamInfroducePerson
+}
diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceCoordinator.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceCoordinator.swift
index 4a69d93..d457bf8 100644
--- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceCoordinator.swift
+++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceCoordinator.swift
@@ -73,3 +73,5 @@ final class IntroduceCoordinator: NavigationControlling, ObservableObject {
replaceStack([route], animated: animated)
}
}
+
+
diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceRoute.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceRoute.swift
index 6a67d16..4d9be14 100644
--- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceRoute.swift
+++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/Flow/IntroduceRoute.swift
@@ -18,4 +18,7 @@ enum IntroduceRoute: Hashable {
// 팀 블로그
case teamBlog
+ // webView
+ case webView(url: String)
+
}
diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/View/IntorduceCoordinatorView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/View/IntorduceCoordinatorView.swift
index d7d94b9..eae6272 100644
--- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/View/IntorduceCoordinatorView.swift
+++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/Coordinator/View/IntorduceCoordinatorView.swift
@@ -9,7 +9,7 @@ import SwiftUI
import SwiftData
struct IntorduceCoordinatorView : View {
- @EnvironmentObject private var coordinator: IntroduceCoordinator
+ @StateObject private var coordinator = IntroduceCoordinator()
var sharedModelContainer: ModelContainer = {
let schema = Schema([
Item.self,
@@ -25,7 +25,7 @@ struct IntorduceCoordinatorView : View {
var body: some View {
NavigationStack(path: $coordinator.path) {
- ContentView()
+ IntroductionMainView(viewModel: IntroductionViewModel(coordinator: coordinator))
.navigationDestination(for: IntroduceRoute.self, destination: makeDestination)
}
.modelContainer(sharedModelContainer)
@@ -38,13 +38,18 @@ extension IntorduceCoordinatorView {
private func makeDestination(for route: IntroduceRoute) -> some View {
switch route {
case .introduceMain:
- ContentView()
+ IntroductionMainView(viewModel: IntroductionViewModel(coordinator: coordinator))
case .teamAgreement:
ContentView()
case .teamIntroduce:
- EmptyView()
+ TeamIntroduceView(coordinator: coordinator)
+ .navigationBarBackButtonHidden()
case .teamBlog:
- EmptyView()
+ TeamBlogView(viewModel: .init(coordinator: coordinator))
+ .navigationBarBackButtonHidden()
+
+ case .webView(let url):
+ WebView(coordinator: coordinator, url: url)
}
}
}
diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/IntroductionRow/IntroductionRowView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/IntroductionRow/IntroductionRowView.swift
index 80652e8..b13c9b1 100644
--- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/IntroductionRow/IntroductionRowView.swift
+++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/IntroductionRow/IntroductionRowView.swift
@@ -8,48 +8,48 @@
import SwiftUI
struct IntroductionRowView: View {
- private let model: IntroductionRowModel
-
- init(model: IntroductionRowModel) {
- self.model = model
- }
-
- var body: some View {
- HStack {
- Circle()
- .frame(width: 42, height: 42)
- VStack(alignment: .leading, spacing: 4) {
- HStack(spacing: 4) {
- Text(model.name)
- .pretendardFont(family: .semiBold, size: 14)
- .foregroundStyle(.textPrimary)
-
- if model.isLeader {
- Image(systemName: "crown.fill")
- .foregroundStyle(Color.yellow)
- .font(.system(size: 12))
- }
- }
-
- Text(model.role)
- .pretendardFont(family: .regular, size: 12)
- .foregroundStyle(.textSecondary)
-
- MBTILabel(mbti: model.mbti)
- }
-
- Spacer()
-
- Image("ArrowRight")
- .foregroundStyle(Color(hex: "717182"))
+ private let model: IntroductionRowModel
+
+ init(model: IntroductionRowModel) {
+ self.model = model
+ }
+
+ var body: some View {
+ HStack {
+ Circle()
+ .frame(width: 42, height: 42)
+ VStack(alignment: .leading, spacing: 4) {
+ HStack(spacing: 4) {
+ Text(model.name)
+ .pretendardFont(family: .semiBold, size: 14)
+ .foregroundStyle(.textPrimary)
+
+ if model.isLeader {
+ Image(systemName: "crown.fill")
+ .foregroundStyle(Color.yellow)
+ .font(.system(size: 12))
+ }
}
- .padding(16)
- .background(Color.staticWhite)
- .clipShape(RoundedRectangle(cornerRadius: 12))
- .shadow(radius: 1)
+
+ Text(model.role)
+ .pretendardFont(family: .regular, size: 12)
+ .foregroundStyle(.textSecondary)
+
+ MBTILabel(mbti: model.mbti)
+ }
+
+ Spacer()
+
+ Image(asset: .arrowRight)
+ .foregroundStyle(Color(hex: "717182"))
}
+ .padding(16)
+ .background(.staticWhite)
+ .clipShape(RoundedRectangle(cornerRadius: 12))
+ .shadow(radius: 1)
+ }
}
#Preview {
- IntroductionRowView(model: IntroductionRowModel.mockData[0])
+ IntroductionRowView(model: IntroductionRowModel.mockData[0])
}
diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/MissionView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/MissionView.swift
index 81bbc47..1564e9c 100644
--- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/MissionView.swift
+++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/MissionView.swift
@@ -10,7 +10,7 @@ import SwiftUI
struct MissionView: View {
var body: some View {
VStack(alignment: .center, spacing: 12) {
- Image("MissionLogo")
+ Image(asset: .missionLogo)
.renderingMode(.original)
Text("우리의 미션")
diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/SkeletonRowView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/SkeletonRowView.swift
index efa7ce5..ac20e9b 100644
--- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/SkeletonRowView.swift
+++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/SkeletonRowView.swift
@@ -8,51 +8,51 @@
import SwiftUI
struct SkeletonRowView: View {
- @State private var isAnimating = false
-
- var body: some View {
- HStack {
- Circle()
- .frame(width: 42, height: 42)
- .foregroundColor(.gray.opacity(0.3))
-
- VStack(alignment: .leading, spacing: 4) {
- RoundedRectangle(cornerRadius: 4)
- .frame(width: 80, height: 16)
- .foregroundColor(.gray.opacity(0.3))
-
- RoundedRectangle(cornerRadius: 4)
- .frame(width: 120, height: 12)
- .foregroundColor(.gray.opacity(0.2))
-
- RoundedRectangle(cornerRadius: 8)
- .frame(width: 50, height: 20)
- .foregroundColor(.gray.opacity(0.2))
- }
-
- Spacer()
-
- RoundedRectangle(cornerRadius: 4)
- .frame(width: 16, height: 16)
- .foregroundColor(.gray.opacity(0.2))
- }
- .padding(16)
- .background(Color.staticWhite)
- .clipShape(RoundedRectangle(cornerRadius: 12))
- .shadow(radius: 1)
- .opacity(isAnimating ? 0.5 : 1.0)
- .animation(.easeInOut(duration: 1.0).repeatForever(autoreverses: true), value: isAnimating)
- .onAppear {
- isAnimating = true
- }
+ @State private var isAnimating = false
+
+ var body: some View {
+ HStack {
+ Circle()
+ .frame(width: 42, height: 42)
+ .foregroundColor(.gray.opacity(0.3))
+
+ VStack(alignment: .leading, spacing: 4) {
+ RoundedRectangle(cornerRadius: 4)
+ .frame(width: 80, height: 16)
+ .foregroundColor(.gray.opacity(0.3))
+
+ RoundedRectangle(cornerRadius: 4)
+ .frame(width: 120, height: 12)
+ .foregroundColor(.gray.opacity(0.2))
+
+ RoundedRectangle(cornerRadius: 8)
+ .frame(width: 50, height: 20)
+ .foregroundColor(.gray.opacity(0.2))
+ }
+
+ Spacer()
+
+ RoundedRectangle(cornerRadius: 4)
+ .frame(width: 16, height: 16)
+ .foregroundColor(.gray.opacity(0.2))
}
+ .padding(16)
+ .background(Color.staticWhite)
+ .clipShape(RoundedRectangle(cornerRadius: 12))
+ .shadow(radius: 1)
+ .opacity(isAnimating ? 0.5 : 1.0)
+ .animation(.easeInOut(duration: 1.0).repeatForever(autoreverses: true), value: isAnimating)
+ .onAppear {
+ isAnimating = true
+ }
+ }
}
#Preview {
- VStack {
- SkeletonRowView()
- SkeletonRowView()
- SkeletonRowView()
- }
- .padding()
+ VStack {
+ SkeletonRowView()
+ SkeletonRowView()
+ SkeletonRowView()
+ }
+ .padding()
}
diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreItem.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreItem.swift
new file mode 100644
index 0000000..e8dd8b1
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreItem.swift
@@ -0,0 +1,50 @@
+//
+// TeamExploreItem.swift
+// TeamIntroduce
+//
+// Created by Wonji Suh on 8/12/25.
+//
+
+import Foundation
+
+enum TeamExploreItem: CaseIterable, Identifiable {
+ case introduction
+ case agreement
+ case blog
+
+ var id: Self { self }
+
+
+ var title: String {
+ switch self {
+ case .introduction:
+ return "팀 소개"
+ case .agreement:
+ return "팀 약속"
+ case .blog:
+ return "팀 블로그"
+ }
+ }
+
+ var subtitle: String {
+ switch self {
+ case .introduction:
+ return "우리 팀의 특징과 목표"
+ case .agreement:
+ return "함께 지켜나갈 소중한 약속들"
+ case .blog:
+ return "팀원들의 블로그 모음"
+ }
+ }
+
+ var imageName: ImageAsset {
+ switch self {
+ case .introduction:
+ return .teamIntroductionLogo
+ case .agreement:
+ return .teamAgreementLogo
+ case .blog:
+ return .teamBlogLogo
+ }
+ }
+}
diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreRowView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreRowView.swift
index fa7649c..0586493 100644
--- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreRowView.swift
+++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/Components/TeamExploreRowView.swift
@@ -7,86 +7,46 @@
import SwiftUI
-enum TeamExploreItem: CaseIterable, Identifiable {
- case introduction
- case agreement
- case blog
-
- var id: Self { self }
-
-
- var title: String {
- switch self {
- case .introduction:
- return "팀 소개"
- case .agreement:
- return "팀 약속"
- case .blog:
- return "팀 블로그"
- }
- }
-
- var subtitle: String {
- switch self {
- case .introduction:
- return "우리 팀의 특징과 목표"
- case .agreement:
- return "함께 지켜나갈 소중한 약속들"
- case .blog:
- return "팀원들의 블로그 모음"
- }
- }
-
- var imageName: String {
- switch self {
- case .introduction:
- return "TeamIntroductionLogo"
- case .agreement:
- return "TeamAgreementLogo"
- case .blog:
- return "TeamBlogLogo"
- }
- }
-}
struct TeamExploreRowView: View {
-
- private let item: TeamExploreItem
-
- init(item: TeamExploreItem) {
- self.item = item
- }
-
- var body: some View {
- HStack {
- Image(item.imageName)
- .frame(width: 42, height: 42)
- VStack(alignment: .leading, spacing: 4) {
- Text(item.title)
- .pretendardFont(family: .semiBold, size: 14)
- .foregroundStyle(.textPrimary)
-
- Text(item.subtitle)
- .pretendardFont(family: .regular, size: 12)
- .foregroundStyle(.textSecondary)
- }
-
- Spacer()
-
- Image("ArrowRight")
- .foregroundStyle(Color(hex: "717182"))
- }
- .padding(16)
- .background(Color.staticWhite)
- .clipShape(RoundedRectangle(cornerRadius: 12))
- .shadow(radius: 1)
+
+ private let item: TeamExploreItem
+
+ init(item: TeamExploreItem) {
+ self.item = item
+ }
+
+ var body: some View {
+ HStack {
+ Image(asset: item.imageName)
+ .frame(width: 42, height: 42)
+
+ VStack(alignment: .leading, spacing: 4) {
+ Text(item.title)
+ .pretendardFont(family: .semiBold, size: 14)
+ .foregroundStyle(.textPrimary)
+
+ Text(item.subtitle)
+ .pretendardFont(family: .regular, size: 12)
+ .foregroundStyle(.textSecondary)
+ }
+
+ Spacer()
+
+ Image(asset: .arrowRight)
+ .foregroundStyle(Color(hex: "717182"))
}
+ .padding(16)
+ .background(Color.staticWhite)
+ .clipShape(RoundedRectangle(cornerRadius: 12))
+ .shadow(radius: 1)
+ }
}
#Preview {
- VStack {
- TeamExploreRowView(item: .introduction)
- TeamExploreRowView(item: .agreement)
- TeamExploreRowView(item: .blog)
- }
+ VStack {
+ TeamExploreRowView(item: .introduction)
+ TeamExploreRowView(item: .agreement)
+ TeamExploreRowView(item: .blog)
+ }
}
diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift
index 6b8e037..0a8d72b 100644
--- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift
+++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/ContentView.swift
@@ -13,13 +13,22 @@ struct ContentView: View {
@Query private var items: [Item]
@EnvironmentObject private var coordinator: IntroduceCoordinator
-
var body: some View {
- VStack {
- Text("main")
- .onTapGesture {
- coordinator.send(.present(.teamAgreement))
- }
+ ZStack {
+ Color.white
+ .edgesIgnoringSafeArea(.all)
+
+ VStack {
+ Spacer()
+
+ Text("main")
+
+ Spacer()
+
+
+ Spacer()
+ .frame(height: 20)
+ }
}
}
@@ -39,6 +48,12 @@ struct ContentView: View {
}
}
+
+extension ContentView {
+
+
+}
+
#Preview {
ContentView()
.modelContainer(for: Item.self, inMemory: true)
diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/IntroductionMainView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/IntroductionMainView.swift
index af42da3..6b793b1 100644
--- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/IntroductionMainView.swift
+++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/IntroductionMainView.swift
@@ -8,54 +8,58 @@
import SwiftUI
struct IntroductionMainView: View {
- @ObservedObject var viewModel: IntroductionViewModel
-
+ @State private var viewModel: IntroductionViewModel
+
init(viewModel: IntroductionViewModel) {
- self.viewModel = viewModel
+ _viewModel = State(initialValue: viewModel)
}
- var body: some View {
- ScrollView {
- VStack(spacing: 16) {
- MissionView()
-
- VStack {
- Text("팀원 소개")
- .pretendardFont(family: .bold, size: 14)
- .foregroundStyle(.textPrimary)
- .frame(maxWidth: .infinity, alignment: .leading)
- .padding(.horizontal, 8)
- if viewModel.isLoading {
- ForEach(0..<3, id: \.self) { _ in
- SkeletonRowView()
- }
- } else {
- ForEach(viewModel.introductions) { model in
- IntroductionRowView(model: model)
- }
- }
- }
-
- VStack {
- Text("더 알아보기")
- .pretendardFont(family: .bold, size: 14)
- .foregroundStyle(.textPrimary)
- .frame(maxWidth: .infinity, alignment: .leading)
- .padding(.horizontal, 8)
-
- ForEach(TeamExploreItem.allCases) { item in
- TeamExploreRowView(item: item)
- }
- }
+
+ var body: some View {
+ ScrollView {
+ VStack(spacing: 16) {
+ MissionView()
+
+ VStack {
+ Text("팀원 소개")
+ .pretendardFont(family: .bold, size: 14)
+ .foregroundStyle(.textPrimary)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .padding(.horizontal, 8)
+ if viewModel.isLoading {
+ ForEach(0..<3, id: \.self) { _ in
+ SkeletonRowView()
}
- .padding(.horizontal, 16)
- .onAppear {
- viewModel.onAppear()
+ } else {
+ ForEach(viewModel.introductions) { model in
+ IntroductionRowView(model: model)
}
+ }
}
+
+ VStack {
+ Text("더 알아보기")
+ .pretendardFont(family: .bold, size: 14)
+ .foregroundStyle(.textPrimary)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .padding(.horizontal, 8)
+
+ ForEach(TeamExploreItem.allCases) { item in
+ TeamExploreRowView(item: item)
+ .onTapGesture {
+ viewModel.send(.tapMoreInfo(item))
+ }
+ }
+ }
+ }
+ .padding(.horizontal, 16)
+ }
+ .onAppear {
+ viewModel.send(.onAppear)
}
+ }
}
#Preview {
- IntroductionMainView(viewModel: IntroductionViewModel())
+ IntroductionMainView(viewModel: IntroductionViewModel())
}
diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/IntroductionViewModel.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/IntroductionViewModel.swift
deleted file mode 100644
index 4fbf61a..0000000
--- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/View/IntroductionViewModel.swift
+++ /dev/null
@@ -1,53 +0,0 @@
-//
-// IntroductionViewModel.swift
-// TeamIntroduce
-//
-// Created by 홍석현 on 8/11/25.
-//
-
-import Foundation
-import SwiftUI
-
-@MainActor
-final class IntroductionViewModel: ObservableObject {
-
- // MARK: - Published Properties
- @Published private(set) var introductions: [IntroductionRowModel] = []
- @Published private(set) var isLoading = false
-
- // MARK: - Public Methods
-
- /// onAppear 시 호출되는 데이터 페치 메서드
- func onAppear() {
- Task {
- await fetchIntroductions()
- }
- }
-
- /// 데이터를 새로고침하는 메서드
- func refresh() {
- Task {
- await fetchIntroductions()
- }
- }
-
- // MARK: - Private Methods
-
- /// 팀원 소개 데이터를 가져오는 메서드
- private func fetchIntroductions() async {
- isLoading = true
-
- do {
- // 실제 API 호출 시뮬레이션 (2초 딜레이)
- try await Task.sleep(nanoseconds: 2_000_000_000)
-
- // Mock 데이터 로드
- introductions = IntroductionRowModel.mockData
-
- } catch {
- introductions = []
- }
-
- isLoading = false
- }
-}
diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/ViewModel/IntroductionViewModel.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/ViewModel/IntroductionViewModel.swift
new file mode 100644
index 0000000..0bc1c23
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/IntroduceMain/ViewModel/IntroductionViewModel.swift
@@ -0,0 +1,76 @@
+//
+// IntroductionViewModel.swift
+// TeamIntroduce
+//
+// Created by 홍석현 on 8/11/25.
+//
+
+import Foundation
+import SwiftUI
+import Combine
+
+@MainActor
+@Observable
+final class IntroductionViewModel {
+
+ // MARK: - State
+ private(set) var introductions: [IntroductionRowModel] = []
+ private(set) var isLoading = false
+
+ // 네비게이션을 코디네이터로 위임하는 라우팅 클로저
+ private let route: (IntroduceCoordinator.Action) -> Void
+
+ // MARK: - Action (뷰에서 이 enum만 쓰면 됩니다)
+ enum Action {
+ case onAppear
+ case refresh
+ case tapMoreInfo(TeamExploreItem)
+ }
+
+ // MARK: - Init
+ /// 기본값: no-op(코디네이터 주입 안 해도 안전)
+ init(route: @escaping (IntroduceCoordinator.Action) -> Void = { _ in }) {
+ self.route = route
+ }
+
+ /// 편의 이니셜라이저: 코디네이터 주입
+ convenience init(coordinator: IntroduceCoordinator?) {
+ if let coordinator {
+ self.init(route: { [weak coordinator] action in coordinator?.send(action) })
+ } else {
+ self.init() // no-op
+ }
+ }
+
+ // MARK: - Single entrypoint
+ func send(_ action: Action) {
+ switch action {
+ case .onAppear, .refresh:
+ Task { await fetchIntroductions() }
+
+ case .tapMoreInfo(let moreInfo):
+ switch moreInfo {
+ case .introduction:
+ route(.present(.teamIntroduce))
+ case .agreement:
+ route(.present(.teamAgreement))
+ case .blog:
+ route(.present(.teamBlog))
+ }
+ }
+ }
+
+ // MARK: - Private
+ private func fetchIntroductions() async {
+ isLoading = true
+ defer { isLoading = false }
+
+ do {
+ // mock delay
+ try await Task.sleep(nanoseconds: 2_000_000_000)
+ introductions = IntroductionRowModel.mockData
+ } catch {
+ introductions = []
+ }
+ }
+}
diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/Root/RootView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/Root/RootView.swift
index f1ce0c8..51db37e 100644
--- a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/Root/RootView.swift
+++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/Root/RootView.swift
@@ -8,13 +8,15 @@
import SwiftUI
struct RootView: View {
- @StateObject var coordinator = IntroduceCoordinator()
+// @StateObject var coordinator = IntroduceCoordinator()
var body: some View {
IntorduceCoordinatorView()
- .environmentObject(coordinator)
+// .environmentObject(coordinator)
}
}
#Preview {
RootView()
}
+
+
diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/BlogItem.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/BlogItem.swift
new file mode 100644
index 0000000..49247a9
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/BlogItem.swift
@@ -0,0 +1,15 @@
+//
+// BlogItem.swift
+// TeamIntroduce
+//
+// Created by Wonji Suh on 8/12/25.
+//
+
+import Foundation
+
+struct BlogItem: Identifiable {
+ let id = UUID()
+ let name: String
+ let blogTitle: String
+ let blogLink: String
+}
diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift
new file mode 100644
index 0000000..ca13ab7
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogView.swift
@@ -0,0 +1,212 @@
+//
+// TeamBlogView.swift
+// TeamIntroduce
+//
+// Created by Wonji Suh on 8/12/25.
+//
+
+import SwiftUI
+
+import SwiftUI
+
+struct TeamBlogView: View {
+ @Bindable var viewModel: TeamBlogViewModel
+
+ init(viewModel: TeamBlogViewModel) {
+ self.viewModel = viewModel
+ }
+
+ var body: some View {
+ ZStack {
+ Color.white
+ .edgesIgnoringSafeArea(.all)
+
+ VStack {
+ Spacer().frame(height: 14)
+
+ CustomNavigationBackBar(text: "팀블로그") {
+ viewModel.send(.backToRoot)
+ }
+
+ Spacer().frame(height: 20)
+
+ blogHeaderView()
+
+ Spacer().frame(height: 10)
+
+ blogList()
+
+ Spacer()
+
+ blogHintBanner()
+
+ Spacer().frame(height: 30)
+ }
+ }
+ .onAppear {
+ viewModel.send(.onAppear)
+ }
+ }
+}
+
+extension TeamBlogView {
+
+ @ViewBuilder
+ private func blogHeaderView() -> some View {
+ VStack(alignment: .center) {
+ Spacer().frame(height: 16)
+
+ Circle()
+ .fill(.gray40)
+ .frame(width: 56, height: 56)
+ .overlay {
+ Image(asset: .glabal)
+ .resizable()
+ .scaledToFit()
+ .frame(width: 30, height: 30)
+ }
+
+ Spacer().frame(height: 10)
+
+ HStack {
+ Spacer()
+ Text("팀원들의 블로그")
+ .pretendardFont(family: .regular, size: 13)
+ .foregroundStyle(.staticBlack)
+ Spacer()
+ }
+
+ Spacer().frame(height: 10)
+
+ Text("각자 공부한 내용및 경험을 공유 하는 공간입니다.")
+ .pretendardFont(family: .regular, size: 13)
+ .foregroundStyle(.blueGray)
+
+ Spacer().frame(height: 16)
+
+ }
+ .background(
+ RoundedRectangle(cornerRadius: 12)
+ .fill(.staticWhite)
+ .shadow(color: .shadowColor, radius: 2)
+ )
+ .padding(.horizontal, 16)
+ }
+
+ // 💡 순차 애니메이션 blog 리스트
+ @ViewBuilder
+ private func blogList() -> some View {
+ if !viewModel.isLoading {
+
+
+ VStack(spacing: 12) {
+ ForEach(Array(viewModel.blogs.indices), id: \.self) { index in
+ let blog = viewModel.blogs[index]
+
+ blogListitem(
+ name: blog.name,
+ blogTitle: blog.blogTitle,
+ blogLink: blog.blogLink
+ ) { link in
+ viewModel.send(.presentWebView(url: link))
+ }
+ .opacity(index <= viewModel.currentMaxIndex ? 1 : 0)
+ .offset(y: index <= viewModel.currentMaxIndex ? 0 : 20)
+ .onAppear {
+ guard index > viewModel.currentMaxIndex else { return }
+ let delay = 0.25 + 0.12 * Double(index) // ⏱ 첫 대기, 카드 간 텀 조정
+ DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
+ withAnimation(.spring(response: 0.8, dampingFraction: 0.85)) {
+ viewModel.currentMaxIndex = index
+ }
+ }
+ }
+ }
+ }
+ } else {
+ ForEach(0..<3, id: \.self) { _ in
+ SkeletonRowView()
+ }
+ }
+ }
+
+ @ViewBuilder
+ private func blogListitem(
+ name: String,
+ blogTitle: String,
+ blogLink: String,
+ action: @escaping (String) -> Void
+ ) -> some View {
+ VStack {
+ HStack {
+ Circle()
+ .fill(.gray.opacity(0.3))
+ .frame(width: 40, height: 40)
+
+ VStack(alignment: .leading) {
+ HStack {
+ Text(name)
+ .pretendardFont(family: .regular, size: 12)
+ .foregroundStyle(.textSecondary)
+
+ Image(asset: .link)
+ .resizable()
+ .scaledToFit()
+ .frame(width: 15, height: 15)
+ .onTapGesture {
+ action(blogLink)
+ }
+
+ Spacer()
+ }
+
+ Text(blogTitle)
+ .pretendardFont(family: .regular, size: 12)
+ .foregroundStyle(.textGray100)
+
+ Text(blogLink)
+ .pretendardFont(family: .light, size: 14)
+ .foregroundStyle(.basicBlack)
+ .onTapGesture {
+ action(blogLink)
+ }
+ }
+
+ Spacer()
+ }
+ .padding(16)
+ }
+ .background(
+ RoundedRectangle(cornerRadius: 12)
+ .fill(.staticWhite)
+ .shadow(color: .shadowColor, radius: 2)
+ )
+ .padding(.horizontal, 16)
+ }
+
+ @ViewBuilder
+ private func blogHintBanner() -> some View {
+ VStack {
+ HStack {
+ Spacer()
+
+ Text("💡 블로그 링크를 탭하면 새 탭에서 열립니다")
+ .pretendardFont(family: .light, size: 12)
+ .foregroundStyle(.basicBlack)
+
+ Spacer()
+ }
+ .padding(.vertical, 16)
+ }
+ .background(
+ RoundedRectangle(cornerRadius: 12)
+ .fill(.staticWhite)
+ .shadow(color: .shadowColor, radius: 2)
+ )
+ .padding(.horizontal, 16)
+ }
+}
+
+#Preview {
+ TeamBlogView(viewModel: .init())
+}
diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift
new file mode 100644
index 0000000..a4db07d
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamBlog/TeamBlogViewModel.swift
@@ -0,0 +1,85 @@
+//
+// TeamBlogViewModel.swift
+// TeamIntroduce
+//
+// Created by Wonji Suh on 8/12/25.
+//
+
+import SwiftUI
+import Combine
+
+import SwiftUI
+import Observation
+
+@MainActor
+@Observable
+final class TeamBlogViewModel {
+ private(set) var isLoading = false
+ var currentMaxIndex: Int = -1
+
+ // 라우팅
+ private let route: (IntroduceCoordinator.Action) -> Void
+ private let goBack: () -> Void
+
+ let blogs: [BlogItem] = [
+ .init(name: "김민희",
+ blogTitle: "모바일개발과 크로스플랫폼 기술을 공유합니다",
+ blogLink: "https://0minnie0.tistory.com/"),
+ .init(name: "서원지",
+ blogTitle: "모바일개발과 크로스플랫폼 기술을 공유합니다",
+ blogLink: "https://velog.io/@suhwj/posts"),
+ .init(name: "홍석현",
+ blogTitle: "모바일개발과 크로스플랫폼 기술을 공유합니다",
+ blogLink: "https://velog.io/@gustjrghd/posts")
+ ]
+
+ // MARK: - Init
+ init(
+ route: @escaping (IntroduceCoordinator.Action) -> Void = { _ in },
+ goBack: @escaping () -> Void = {}
+ ) {
+ self.route = route
+ self.goBack = goBack
+ }
+
+ // 코디네이터 주입 편의 생성자
+ convenience init(coordinator: IntroduceCoordinator?) {
+ if let coordinator {
+ self.init(
+ route: { [weak coordinator] action in coordinator?.send(action) },
+ goBack: { [weak coordinator] in coordinator?.goBack() }
+ )
+ } else {
+ self.init()
+ }
+ }
+
+ // MARK: - Action
+ enum Action {
+ case onAppear
+ case refresh
+ case presentWebView(url: String)
+ case backToRoot
+ }
+
+ // MARK: - Single entrypoint
+ func send(_ action: Action) {
+ switch action {
+ case .onAppear, .refresh:
+ Task { await fetchIntroductions() }
+
+ case .presentWebView(let url):
+ route(.present(.webView(url: url)))
+
+ case .backToRoot:
+ goBack()
+ }
+ }
+
+ // MARK: - Private
+ private func fetchIntroductions() async {
+ isLoading = true
+ defer { isLoading = false }
+ try? await Task.sleep(for: .seconds(0.3))
+ }
+}
diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/IntroduceItem.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/IntroduceItem.swift
new file mode 100644
index 0000000..7503d88
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/IntroduceItem.swift
@@ -0,0 +1,15 @@
+//
+// IntroduceItem.swift
+// TeamIntroduce
+//
+// Created by Wonji Suh on 8/12/25.
+//
+
+import Foundation
+
+struct IntroduceItem: Identifiable {
+ let id = UUID()
+ let image: ImageAsset
+ let title: String
+ let subtitle: String
+}
diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TeamIntroduceView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TeamIntroduceView.swift
new file mode 100644
index 0000000..00b2c79
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TeamIntroduceView.swift
@@ -0,0 +1,170 @@
+//
+// TeamIntroduceView.swift
+// TeamIntroduce
+//
+// Created by Wonji Suh on 8/12/25.
+//
+
+import SwiftUI
+
+
+struct TeamIntroduceView: View {
+ @ObservedObject var coordinator: IntroduceCoordinator
+
+ // 현재까지 보여줄 수 있는 최대 인덱스
+ @State private var currentMaxIndex: Int = -1
+
+ // 소개 아이템 배열
+ private let introduceItems: [IntroduceItem] = [
+ .init(image: .teamInfroducePerson, title: "다양성 존중", subtitle: "각자의 강점과 개성을 인정하고 서로 보완하며 성장합니다."),
+ .init(image: .teamInfroduceAccident, title: "창의적 사고", subtitle: "새로운 아이디어를 자유롭게 제안하고 실험하는 문화를 추구 합니다."),
+ .init(image: .teamInfroduceHeart, title: "따뜻한 소통", subtitle: "솔직하고 건설적인 피드백으로 서로를 도우며 성장합니다."),
+ .init(image: .teamInfroduceCircle, title: "목표지향", subtitle: "명확한 목표를 설정하고 함께 달성해나가는 팀워크를 발휘합니다.")
+ ]
+
+ var body: some View {
+ ZStack {
+ Color.staticWhite
+ .edgesIgnoringSafeArea(.all)
+
+ VStack {
+ Spacer().frame(height: 14)
+
+ CustomNavigationBackBar(text: "팀소개") {
+ coordinator.goBack()
+ }
+
+ Spacer().frame(height: 20)
+
+ teamIntorduceHeader()
+ teamIntroduceList()
+ introduceList()
+
+ Spacer()
+ }
+ }
+ }
+}
+
+extension TeamIntroduceView {
+ // 타이틀 박스
+ @ViewBuilder
+ private func teamIntorduceHeader() -> some View {
+ VStack {
+ Text("우리 팀의 궁극적인 목표")
+ .pretendardFont(family: .semiBold, size: 16)
+ .foregroundStyle(.gray60)
+
+ Image(asset: .teamiIntroduce)
+ .resizable()
+ .scaledToFit()
+ .frame(width: 56, height: 56)
+
+ Spacer().frame(height: 10)
+
+ HStack {
+ Spacer()
+ TypingText(
+ text: "안녕하세요 1조입니다! 👋",
+ font: .pretendardFontFamily(family: .bold, size: 16),
+ perChar: 0.06,
+ startDelay: 0.15,
+ showsCursor: false
+ )
+ Spacer()
+ }
+ }
+ .padding(16)
+ .background(
+ RoundedRectangle(cornerRadius: 12)
+ .fill(.staticWhite)
+ .shadow(color: .shadowColor, radius: 2)
+ )
+ .padding(.horizontal, 16)
+ }
+
+ // 팀 특징 타이틀
+ @ViewBuilder
+ fileprivate func teamIntroduceList() -> some View {
+ HStack {
+ Text("우리 팀만의 특징")
+ .pretendardFont(family: .regular, size: 16)
+ .foregroundStyle(.basicBlack)
+ Spacer()
+ }
+ .padding(16)
+ }
+
+ // 애니메이션되며 등장하는 소개 리스트
+ @ViewBuilder
+ private func introduceList() -> some View {
+ let indices = Array(introduceItems.indices)
+
+ VStack(spacing: 12) {
+ ForEach(indices, id: \.self) { index in
+ let item = introduceItems[index]
+
+ introduceItem(
+ image: item.image,
+ title: item.title,
+ subtitle: item.subtitle
+ )
+ .opacity(index <= currentMaxIndex ? 1 : 0)
+ .offset(y: index <= currentMaxIndex ? 0 : 12)
+ .onAppear {
+ // 이미 등장한 인덱스는 무시
+ guard index > currentMaxIndex else { return }
+ let delay = 0.25 + 0.15 * Double(index)
+ DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
+ withAnimation(.spring(response: 0.5, dampingFraction: 0.85)) {
+ currentMaxIndex = index
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // 개별 아이템 뷰
+ @ViewBuilder
+ fileprivate func introduceItem(
+ image: ImageAsset,
+ title: String,
+ subtitle: String
+ ) -> some View {
+ VStack {
+ HStack {
+ Image(asset: image)
+ .resizable()
+ .scaledToFit()
+ .frame(width: 35, height: 35)
+
+ Spacer().frame(width: 10)
+
+ VStack(alignment: .leading) {
+ Text(title)
+ .pretendardFont(family: .regular, size: 12)
+ .foregroundStyle(.textSecondary100)
+ Spacer().frame(height: 4)
+ Text(subtitle)
+ .pretendardFont(family: .regular, size: 14)
+ .foregroundStyle(.textPrimary)
+ }
+
+ Spacer()
+ }
+ .padding(16)
+ }
+ .background(
+ RoundedRectangle(cornerRadius: 12)
+ .fill(.staticWhite)
+ .shadow(color: .shadowColor, radius: 2)
+ )
+ .padding(.horizontal, 16)
+ }
+}
+
+#Preview {
+ @Previewable @StateObject var coordinator: IntroduceCoordinator = IntroduceCoordinator()
+ TeamIntroduceView(coordinator: coordinator)
+}
diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TypingText.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TypingText.swift
new file mode 100644
index 0000000..646fae4
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/TeamIntroduce/TypingText.swift
@@ -0,0 +1,62 @@
+//
+// TypingText.swift
+// TeamIntroduce
+//
+// Created by Wonji Suh on 8/12/25.
+//
+
+import SwiftUI
+
+struct TypingText: View {
+ let text: String
+ var font: Font = .pretendardFontFamily(family: .semiBold, size: 20)
+ var perChar: Double = 0.05 // 글자당 지연(초)
+ var startDelay: Double = 0.0 // 시작 지연(초)
+ var showsCursor: Bool = true // 커서 표시 여부
+
+ @Environment(\.accessibilityReduceMotion) private var reduceMotion
+ @State private var displayed: String = ""
+ @State private var blink = false
+ @State private var task: Task?
+
+ var body: some View {
+ HStack(spacing: 0) {
+ Text(displayed).font(font)
+
+ if showsCursor {
+ Text("|")
+ .font(font)
+ .opacity(blink ? 0 : 1)
+ .animation(.easeInOut(duration: 0.6).repeatForever(), value: blink)
+ .accessibilityHidden(true)
+ }
+ }
+ .onAppear { startTyping() }
+ .onChange(of: text) { _ , _ in startTyping() }
+ .onDisappear { task?.cancel() }
+ }
+
+ private func startTyping() {
+ task?.cancel()
+
+ guard !reduceMotion else {
+ displayed = text
+ blink.toggle()
+ return
+ }
+
+ displayed = ""
+ blink = false
+
+ task = Task {
+ if startDelay > 0 {
+ try? await Task.sleep(nanoseconds: UInt64(startDelay * 1_000_000_000))
+ }
+ for ch in text {
+ displayed.append(ch)
+ try? await Task.sleep(nanoseconds: UInt64(perChar * 1_000_000_000))
+ }
+ await MainActor.run { blink = true }
+ }
+ }
+}
diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebRepresentableView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebRepresentableView.swift
new file mode 100644
index 0000000..8ca5a64
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebRepresentableView.swift
@@ -0,0 +1,144 @@
+//
+// WebRepresentableView.swift
+// TeamIntroduce
+//
+// Created by Wonji Suh on 8/12/25.
+//
+
+import SwiftUI
+import WebKit
+
+
+import SwiftUI
+import WebKit
+
+ struct WebRepresentableView: UIViewRepresentable {
+
+ // MARK: - URL to load
+ var urlToLoad: String
+
+ init(urlToLoad: String) {
+ self.urlToLoad = urlToLoad
+ }
+
+ func makeCoordinator() -> Coordinator {
+ Coordinator(self)
+ }
+
+ func makeUIView(context: Context) -> UIView {
+ // 컨테이너
+ let containerView = UIView()
+ containerView.backgroundColor = .white
+
+ // WKWebView
+ let configuration = WKWebViewConfiguration()
+ let webView = WKWebView(frame: .zero, configuration: configuration)
+ webView.scrollView.showsVerticalScrollIndicator = false
+ webView.scrollView.minimumZoomScale = 1.0
+ webView.scrollView.maximumZoomScale = 1.0
+ webView.navigationDelegate = context.coordinator
+ webView.uiDelegate = context.coordinator
+ webView.allowsLinkPreview = true
+ webView.backgroundColor = .white
+ webView.translatesAutoresizingMaskIntoConstraints = false
+
+ let spinner = UIActivityIndicatorView(style: .large)
+ spinner.translatesAutoresizingMaskIntoConstraints = false
+ spinner.hidesWhenStopped = true
+ spinner.isHidden = false
+
+ containerView.addSubview(webView)
+ containerView.addSubview(spinner)
+
+ NSLayoutConstraint.activate([
+ // WebView는 전체
+ webView.topAnchor.constraint(equalTo: containerView.topAnchor),
+ webView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
+ webView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
+ webView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
+
+ // 스피너는 중앙
+ spinner.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
+ spinner.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
+ ])
+
+ // 코디네이터가 참조 보관
+ context.coordinator.webView = webView
+ context.coordinator.spinner = spinner
+
+ // 로드 직전에 스피너 시작
+ spinner.startAnimating()
+ spinner.alpha = 1
+
+ // 로드
+ _Concurrency.Task {
+ await loadURLInWebView(urlToLoad: urlToLoad, webView: webView)
+ }
+
+ return containerView
+ }
+
+ func loadURLInWebView(urlToLoad: String, webView: WKWebView) async {
+ guard let url = URL(string: urlToLoad) else {
+ print("INVALID URL")
+ return
+ }
+ let request = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy)
+
+ await MainActor.run {
+ webView.configuration.upgradeKnownHostsToHTTPS = true
+ webView.configuration.preferences.minimumFontSize = 16
+ webView.load(request)
+ }
+ }
+
+ func updateUIView(_ uiView: UIView, context: Context) {
+ // 필요 시 업데이트
+ }
+
+ // MARK: - Coordinator
+ class Coordinator: NSObject, WKNavigationDelegate, WKUIDelegate {
+ var parent: WebRepresentableView
+ weak var webView: WKWebView?
+ weak var spinner: UIActivityIndicatorView?
+
+ init(_ parent: WebRepresentableView) {
+ self.parent = parent
+ }
+
+ // MARK: - WKNavigationDelegate
+
+ func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
+ // 로딩 시작 → 스피너 표시
+ DispatchQueue.main.async { [weak self ] in
+ self?.spinner?.alpha = 1
+ self?.spinner?.startAnimating()
+ }
+ }
+
+ func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
+ // 로딩 완료 → 스피너 숨김(페이드아웃)
+ hideSpinner()
+ }
+
+ func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
+ hideSpinner()
+ }
+
+ func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
+ hideSpinner()
+ }
+
+ private func hideSpinner() {
+ DispatchQueue.main.async { [weak self ] in
+ guard let spinner = self?.spinner else { return }
+ UIView.animate(withDuration: 0.2, animations: {
+ spinner.alpha = 0
+ }, completion: { _ in
+ spinner.stopAnimating()
+ spinner.alpha = 1 // 다음 로딩 대비 초기화
+ })
+ }
+ }
+ }
+}
diff --git a/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebView.swift b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebView.swift
new file mode 100644
index 0000000..7c4180b
--- /dev/null
+++ b/TeamIntroduce/TeamIntroduce/Sources/Presnetaion/WebView/WebView.swift
@@ -0,0 +1,46 @@
+//
+// WebView.swift
+// TeamIntroduce
+//
+// Created by Wonji Suh on 8/12/25.
+//
+
+import SwiftUI
+
+ struct WebView: View {
+
+ @ObservedObject var coordinator: IntroduceCoordinator
+ var url: String
+
+ init(
+ coordinator: IntroduceCoordinator,
+ url: String
+ ) {
+ self._coordinator = ObservedObject(wrappedValue: coordinator)
+ self.url = url
+ }
+
+ var body: some View {
+ ZStack {
+ Color.white
+ .edgesIgnoringSafeArea(.all)
+
+ VStack {
+ Spacer()
+ .frame(height: 14)
+
+ CustomNavigationBackBar(text: ""){
+ coordinator.goBack()
+ }
+
+ Spacer()
+ .frame(height: 16)
+
+ WebRepresentableView(urlToLoad: url)
+ }
+ .navigationBarBackButtonHidden(true)
+ }
+ }
+}
+
+