1.0.2
This commit is contained in:
parent
3b593f564e
commit
48f5c0309d
3 changed files with 483 additions and 622 deletions
|
@ -18,6 +18,11 @@ struct PDSCredentials: Sendable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shared date formatters to improve performance
|
||||||
|
extension ISO8601DateFormatter {
|
||||||
|
static let shared = ISO8601DateFormatter()
|
||||||
|
}
|
||||||
|
|
||||||
// Invite code model
|
// Invite code model
|
||||||
struct InviteCode: Identifiable, Sendable {
|
struct InviteCode: Identifiable, Sendable {
|
||||||
var id: String
|
var id: String
|
||||||
|
@ -26,6 +31,20 @@ struct InviteCode: Identifiable, Sendable {
|
||||||
var createdAt: Date
|
var createdAt: Date
|
||||||
var disabled: Bool
|
var disabled: Bool
|
||||||
var isDisabled: Bool { disabled } // For backwards compatibility
|
var isDisabled: Bool { disabled } // For backwards compatibility
|
||||||
|
|
||||||
|
// Returns a formatted date string for display
|
||||||
|
var formattedDate: String {
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.dateStyle = .medium
|
||||||
|
formatter.timeStyle = .short
|
||||||
|
return formatter.string(from: createdAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the number of available uses
|
||||||
|
var availableUses: Int {
|
||||||
|
let usedCount = uses?.count ?? 0
|
||||||
|
return max(0, 1 - usedCount) // Assuming default of 1 use per code
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invite use model
|
// Invite use model
|
||||||
|
@ -33,6 +52,20 @@ struct CodeUse: Codable, Identifiable, Sendable {
|
||||||
var id: String { usedBy }
|
var id: String { usedBy }
|
||||||
var usedBy: String
|
var usedBy: String
|
||||||
var usedAt: String
|
var usedAt: String
|
||||||
|
|
||||||
|
// Parsed date for the usedAt string
|
||||||
|
var date: Date? {
|
||||||
|
ISO8601DateFormatter.shared.date(from: usedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formatted date for display
|
||||||
|
var formattedDate: String {
|
||||||
|
guard let date = date else { return "Unknown date" }
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.dateStyle = .medium
|
||||||
|
formatter.timeStyle = .short
|
||||||
|
return formatter.string(from: date)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// User model
|
// User model
|
||||||
|
@ -46,6 +79,14 @@ class PDSUser: Identifiable, Hashable, Sendable, ObservableObject {
|
||||||
@Published var description: String
|
@Published var description: String
|
||||||
@Published var isActive: Bool = true
|
@Published var isActive: Bool = true
|
||||||
|
|
||||||
|
// Formatted date for display
|
||||||
|
var formattedJoinDate: String {
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.dateStyle = .medium
|
||||||
|
formatter.timeStyle = .none
|
||||||
|
return formatter.string(from: joinedAt)
|
||||||
|
}
|
||||||
|
|
||||||
init(id: String, handle: String, displayName: String, description: String, joinedAt: Date, avatar: URL?, isActive: Bool = true) {
|
init(id: String, handle: String, displayName: String, description: String, joinedAt: Date, avatar: URL?, isActive: Bool = true) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.handle = handle
|
self.handle = handle
|
||||||
|
@ -66,6 +107,23 @@ class PDSUser: Identifiable, Hashable, Sendable, ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shared DateFormatter for consistent date formatting across the app
|
||||||
|
extension DateFormatter {
|
||||||
|
static let shared: DateFormatter = {
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.dateStyle = .medium
|
||||||
|
formatter.timeStyle = .short
|
||||||
|
return formatter
|
||||||
|
}()
|
||||||
|
|
||||||
|
static let dateOnly: DateFormatter = {
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.dateStyle = .medium
|
||||||
|
formatter.timeStyle = .none
|
||||||
|
return formatter
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
// Auth state
|
// Auth state
|
||||||
enum AuthState: Sendable {
|
enum AuthState: Sendable {
|
||||||
case loggedOut
|
case loggedOut
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -27,6 +27,9 @@ struct LoginView: View {
|
||||||
.autocapitalization(.none)
|
.autocapitalization(.none)
|
||||||
.disableAutocorrection(true)
|
.disableAutocorrection(true)
|
||||||
.keyboardType(.URL)
|
.keyboardType(.URL)
|
||||||
|
.placeholder(when: serverURL.isEmpty) {
|
||||||
|
Text("example.com").foregroundColor(.gray.opacity(0.5))
|
||||||
|
}
|
||||||
|
|
||||||
SecureField("Admin Password", text: $password)
|
SecureField("Admin Password", text: $password)
|
||||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||||
|
@ -114,9 +117,9 @@ struct LoginView: View {
|
||||||
.onAppear {
|
.onAppear {
|
||||||
// Add some example connection info
|
// Add some example connection info
|
||||||
debugLogs.append("Example URLs:")
|
debugLogs.append("Example URLs:")
|
||||||
debugLogs.append("https://bsky.social - Main Bluesky server")
|
debugLogs.append("bsky.social - Main Bluesky server")
|
||||||
debugLogs.append("https://bsky.atri.dad - Your local server")
|
debugLogs.append("bsky.atri.dad - Your local server")
|
||||||
debugLogs.append("http://localhost:3000 - Local development PDS")
|
debugLogs.append("localhost:3000 - Local development PDS")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,19 +134,8 @@ struct LoginView: View {
|
||||||
// Add debug info
|
// Add debug info
|
||||||
debugLogs.append("Attempting login to: \(serverURL)")
|
debugLogs.append("Attempting login to: \(serverURL)")
|
||||||
|
|
||||||
// Sanitize the URL to ensure it has the correct format
|
// Normalize the URL to ensure it has the correct format
|
||||||
var cleanURL = serverURL.trimmingCharacters(in: .whitespacesAndNewlines)
|
var cleanURL = normalizeServerURL(serverURL)
|
||||||
if !cleanURL.hasPrefix("http://") && !cleanURL.hasPrefix("https://") {
|
|
||||||
cleanURL = "http://\(cleanURL)"
|
|
||||||
debugLogs.append("Added http:// prefix: \(cleanURL)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove trailing slash if present
|
|
||||||
if cleanURL.hasSuffix("/") {
|
|
||||||
cleanURL.removeLast()
|
|
||||||
debugLogs.append("Removed trailing slash: \(cleanURL)")
|
|
||||||
}
|
|
||||||
|
|
||||||
debugLogs.append("Using final URL: \(cleanURL)")
|
debugLogs.append("Using final URL: \(cleanURL)")
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
|
@ -157,4 +149,44 @@ struct LoginView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func normalizeServerURL(_ url: String) -> String {
|
||||||
|
// Remove any leading/trailing whitespace
|
||||||
|
var cleanURL = url.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
|
||||||
|
// Strip any existing protocol prefix
|
||||||
|
if cleanURL.hasPrefix("http://") {
|
||||||
|
cleanURL = String(cleanURL.dropFirst(7))
|
||||||
|
debugLogs.append("Removed http:// prefix")
|
||||||
|
} else if cleanURL.hasPrefix("https://") {
|
||||||
|
cleanURL = String(cleanURL.dropFirst(8))
|
||||||
|
debugLogs.append("Removed https:// prefix")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove trailing slash if present
|
||||||
|
if cleanURL.hasSuffix("/") {
|
||||||
|
cleanURL.removeLast()
|
||||||
|
debugLogs.append("Removed trailing slash")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add https:// prefix (always use HTTPS by default)
|
||||||
|
cleanURL = "https://\(cleanURL)"
|
||||||
|
debugLogs.append("Added https:// prefix")
|
||||||
|
|
||||||
|
return cleanURL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder extension for TextField
|
||||||
|
extension View {
|
||||||
|
func placeholder<Content: View>(
|
||||||
|
when shouldShow: Bool,
|
||||||
|
alignment: Alignment = .leading,
|
||||||
|
@ViewBuilder placeholder: () -> Content
|
||||||
|
) -> some View {
|
||||||
|
ZStack(alignment: alignment) {
|
||||||
|
placeholder().opacity(shouldShow ? 1 : 0)
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue