Deploy Ladill Mini / deploy (push) Successful in 35s
Ship full-featured mobile clients with auth, payments, QRs, push notifications, and iOS polish including Figtree typography, app icons, and QR previews. Co-authored-by: Cursor <cursoragent@cursor.com>
103 lines
3.1 KiB
Swift
103 lines
3.1 KiB
Swift
import SwiftUI
|
|
|
|
struct LadillCard<Content: View>: View {
|
|
@ViewBuilder let content: Content
|
|
|
|
var body: some View {
|
|
content
|
|
.padding(16)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.background(LadillColors.surface)
|
|
.overlay(RoundedRectangle(cornerRadius: 16).stroke(LadillColors.border, lineWidth: 1))
|
|
.clipShape(RoundedRectangle(cornerRadius: 16))
|
|
}
|
|
}
|
|
|
|
struct StatCard: View {
|
|
let label: String
|
|
let value: String
|
|
var accent = false
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Text(label)
|
|
.font(.ladill(size: 12, weight: .medium))
|
|
.foregroundStyle(accent ? LadillColors.indigoText : LadillColors.muted)
|
|
Text(value)
|
|
.font(.ladill(size: 22, weight: .semibold))
|
|
.foregroundStyle(accent ? LadillColors.money : LadillColors.ink)
|
|
}
|
|
.padding(16)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.background(accent ? LadillColors.indigoSoft : LadillColors.surface)
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: 16)
|
|
.stroke(accent ? LadillColors.indigo.opacity(0.2) : LadillColors.border, lineWidth: 1)
|
|
)
|
|
.clipShape(RoundedRectangle(cornerRadius: 16))
|
|
}
|
|
}
|
|
|
|
struct UserAvatar: View {
|
|
let name: String?
|
|
let photoURL: String?
|
|
var size: CGFloat = 40
|
|
var onTap: (() -> Void)?
|
|
|
|
var body: some View {
|
|
let content = ZStack {
|
|
Circle()
|
|
.fill(LadillColors.indigoSoft)
|
|
if let initial = name?.trimmingCharacters(in: .whitespaces).first {
|
|
Text(String(initial).uppercased())
|
|
.font(.ladill(size: size * 0.38, weight: .semibold))
|
|
.foregroundStyle(LadillColors.indigoText)
|
|
} else {
|
|
Image(systemName: "person.fill")
|
|
.font(.system(size: size * 0.4))
|
|
.foregroundStyle(LadillColors.indigoText)
|
|
}
|
|
}
|
|
.frame(width: size, height: size)
|
|
|
|
if let onTap {
|
|
Button(action: onTap) { content }.buttonStyle(.plain)
|
|
} else {
|
|
content
|
|
}
|
|
}
|
|
}
|
|
|
|
struct LoadingView: View {
|
|
var body: some View {
|
|
VStack(spacing: 12) {
|
|
ProgressView().tint(LadillColors.indigo)
|
|
Text("Loading…")
|
|
.font(.ladill(size: 14))
|
|
.foregroundStyle(LadillColors.muted)
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.padding(.vertical, 40)
|
|
}
|
|
}
|
|
|
|
struct ErrorView: View {
|
|
let message: String
|
|
let onRetry: () -> Void
|
|
|
|
var body: some View {
|
|
VStack(spacing: 12) {
|
|
Text(message)
|
|
.font(.ladill(size: 14))
|
|
.foregroundStyle(LadillColors.muted)
|
|
.multilineTextAlignment(.center)
|
|
Button("Try again", action: onRetry)
|
|
.font(.ladill(size: 14, weight: .semibold))
|
|
.foregroundStyle(LadillColors.indigo)
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.padding(.vertical, 40)
|
|
.padding(.horizontal, 20)
|
|
}
|
|
}
|