Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
20 changes: 18 additions & 2 deletions Hustle/App/AppFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ struct AppFeature {
@ObservableState
struct State: Equatable {
var auth = AuthFeature.State()
var main = MainContentFeature.State()
var isAuthenticated = false
}

enum Action {
case auth(AuthFeature.Action)
case main(MainContentFeature.Action)
case onAppear
}

Expand All @@ -26,8 +28,18 @@ struct AppFeature {
AuthFeature()
}

Scope(state: \.main, action: \.main) {
MainContentFeature()
}

Reduce { state, action in
switch action {
case .onAppear:
return .none

case .main(.profile(.logoutTapped)):
return .send(.auth(.signOutbuttonTapped))

case .auth(.authenticationSucceeded):
state.isAuthenticated = true
return .none
Expand All @@ -36,11 +48,15 @@ struct AppFeature {
state.isAuthenticated = false
return .none

case .onAppear:
return .send(.auth(.startListening))
case .auth(.signOutSucceeded):
state.isAuthenticated = false
return .none

case .auth:
return .none

case .main:
return .none
}
}
}
Expand Down
35 changes: 35 additions & 0 deletions Hustle/App/AppView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// AppView.swift
// Hustle
//
// Created by Jay on 11/30/25.
//

import ComposableArchitecture
import SwiftUI

struct AppView: View {
@Bindable var store: StoreOf<AppFeature>

var body: some View {
Group {
if store.isAuthenticated {
MainContentView(store: store.scope(state: \.main, action: \.main))
} else {
AuthView(store: store.scope(state: \.auth, action: \.auth))
}
}
.task {
await store.send(.auth(.startListening)).finish()
}
}
}

#Preview {
AppView(
store: Store(
initialState: AppFeature.State(),
reducer: { AppFeature() }
)
)
}
29 changes: 0 additions & 29 deletions Hustle/App/MainView.swift

This file was deleted.

39 changes: 39 additions & 0 deletions Hustle/Components/CategoryButton.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// CategoryButton.swift
// Hustle
//
// Created by Jay on 11/30/25.
//

import SwiftUI

struct CategoryButton: View {
let category: Category
let isSelected: Bool
let action: () -> Void

var body: some View {
Button(action: action) {
HStack(spacing: 8) {
Image(systemName: category.systemImage)
.font(.system(size: 16, weight: .semibold))
.foregroundColor(DesignConstants.Colors.white)
Text(category.title)
.font(DesignConstants.Fonts.title3Bold)
.foregroundColor(DesignConstants.Colors.white)
.fixedSize(horizontal: true, vertical: false)
}
.padding(.vertical, 10)
.padding(.horizontal, 14)
.frame(height: 32)
.background(DesignConstants.Colors.hustleGreen)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(DesignConstants.Colors.hustleGreen, lineWidth: 1.2)
)
.cornerRadius(20)
}
.buttonStyle(.plain)
.shadow(color: Color.black.opacity(0.04), radius: 4, x: 0, y: 2)
}
}
71 changes: 71 additions & 0 deletions Hustle/Components/ResultsComponents.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// ResultsComponents.swift
// Hustle
//
// Created by Jay on 12/6/25.
//

import SwiftUI

struct FilterChipsRow: View {
let filters: [String]
var includeFilterIcon: Bool = false

var body: some View {
HStack(spacing: 8) {
Image("Filter")
.padding(.horizontal, 12)
.padding(.vertical, 10)
.background(
RoundedRectangle(cornerRadius: 20)
.stroke(DesignConstants.Colors.hustleGreen.opacity(0.5), lineWidth: 1)
)

ForEach(filters, id: \.self) { filter in
HStack(spacing: 6) {
Text(filter)
.font(DesignConstants.Fonts.title3Bold)
.foregroundColor(DesignConstants.Colors.hustleGreen)
Image(systemName: "chevron.down")
.font(DesignConstants.Fonts.title3Bold)
.foregroundColor(DesignConstants.Colors.hustleGreen)
}
.padding(.horizontal, 12)
.padding(.vertical, 10)
.background(
RoundedRectangle(cornerRadius: 20)
.stroke(DesignConstants.Colors.hustleGreen.opacity(0.5), lineWidth: 1)
)
}
}
.padding(.bottom, 16)
.padding(.top, 8)
}
}

struct ServiceResultsList: View {
let services: [Service]
var imageHeight: CGFloat = 240

var body: some View {
VStack(spacing: 16) {
ForEach(services) { service in
ServiceCardLarge(
service: service,
isFavorite: false,
)
.frame(maxWidth: .infinity)
}
}
}
}

#Preview {
VStack(spacing: 24) {
FilterChipsRow(filters: ["Price", "Location", "Ratings"], includeFilterIcon: true)
.padding(.horizontal, 16)

ServiceResultsList(services: SampleData.services, imageHeight: 180)
.padding(.horizontal, 16)
}
}
86 changes: 86 additions & 0 deletions Hustle/Components/ServiceCard.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//
// ServiceCard.swift
// Hustle
//
// Created by Jay on 11/30/25.
//

import SwiftUI

struct ServiceCard: View {
let service: Service
var isFavorite: Bool = false
var cardWidth: CGFloat? = 178
var cardHeight: CGFloat? = 280
var imageHeight: CGFloat = 178

var body: some View {
VStack(alignment: .leading) {
ZStack(alignment: .topTrailing) {
Image(service.serviceImage)
.resizable()
.scaledToFill()
.frame(width: cardWidth, height: imageHeight)
.clipped()

Image(systemName: isFavorite ? "heart.fill" : "heart")
.foregroundColor(isFavorite ? Color.red : Color.white)
.padding(10)
.shadow(color: Color.black.opacity(0.08), radius: 6, x: 0, y: 4)
}

VStack(alignment: .leading){
HStack{
Image(service.providerPFP)
.resizable()
.frame(width: 24, height: 24)
.clipShape(Circle())

Text(service.providerName)
.font(DesignConstants.Fonts.title3Bold)
.foregroundColor(DesignConstants.Colors.black)
.lineLimit(1)
}

Text(service.description)
.font(DesignConstants.Fonts.title4)
.foregroundColor(DesignConstants.Colors.black)
.lineLimit(2, reservesSpace: true)
.frame(maxWidth: .infinity, alignment: .leading)

HStack{
Text(service.price)
.font(DesignConstants.Fonts.title3)
.foregroundColor(DesignConstants.Colors.black)

Spacer()

HStack() {
Image(systemName: "star.fill")
.foregroundColor(DesignConstants.Colors.accentGreen)
.font(.system(size: 16))

Text(String(format: "%.1f", service.rating))
.font(DesignConstants.Fonts.subtitle1)
.foregroundColor(DesignConstants.Colors.black)
}
}

}
.padding(.horizontal, 12)
.padding(.bottom, 16)

}
.frame(width: cardWidth, height: cardHeight, alignment: .top)
.background(
RoundedRectangle(cornerRadius: 10, style: .continuous)
.fill(DesignConstants.Colors.white)
)
.overlay(
RoundedRectangle(cornerRadius: 10, style: .continuous)
.stroke(DesignConstants.Colors.stroke, lineWidth: 1)
)
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.shadow(color: Color.black.opacity(0.05), radius: 6, x: 0, y: 4)
}
}
80 changes: 80 additions & 0 deletions Hustle/Components/ServiceCardLarge.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// ServiceCard.swift
// Hustle
//
// Created by Jay on 11/30/25.
//

import SwiftUI

struct ServiceCardLarge: View {
let service: Service
var isFavorite: Bool = false
var cardWidth: CGFloat? = 364
var cardHeight: CGFloat? = 436
var imageHeight: CGFloat = 360

var body: some View {
VStack(alignment: .leading) {
Image(service.serviceImage)
.resizable()
.scaledToFill()
.frame(width: cardWidth, height: imageHeight)
.clipped()

Spacer()

HStack{
Image(service.providerPFP)
.resizable()
.frame(width: 40, height: 40)
.clipShape(Circle())

VStack{
HStack{
Text(service.providerName)
.font(DesignConstants.Fonts.title3Bold)
.foregroundColor(DesignConstants.Colors.black)
.lineLimit(1)
Spacer()

Image(systemName: "star.fill")
.foregroundColor(DesignConstants.Colors.accentGreen)
.font(.system(size: 16))

Text(String(format: "%.1f", service.rating))
.font(DesignConstants.Fonts.subtitle1)
.foregroundColor(DesignConstants.Colors.secondaryGrey)
}

HStack{
Text(service.description)
.font(DesignConstants.Fonts.title4)
.foregroundColor(DesignConstants.Colors.secondaryGrey)
.lineLimit(1)
.fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: .infinity, alignment: .leading)

Text(service.price)
.font(DesignConstants.Fonts.title4)
.foregroundColor(DesignConstants.Colors.secondaryGrey)
}
}
}
.padding(.horizontal, 16)

Spacer()
}
.frame(width: cardWidth, height: cardHeight, alignment: .top)
.background(
RoundedRectangle(cornerRadius: 16, style: .continuous)
.fill(DesignConstants.Colors.white)
)
.overlay(
RoundedRectangle(cornerRadius: 16, style: .continuous)
.stroke(DesignConstants.Colors.stroke, lineWidth: 1)
)
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.shadow(color: Color.black.opacity(0.05), radius: 6, x: 0, y: 4)
}
}
Loading