diff --git a/src/ClientApp/.eslintrc.json b/src/ClientApp/.eslintrc.json
index bffb357..f9b11f1 100644
--- a/src/ClientApp/.eslintrc.json
+++ b/src/ClientApp/.eslintrc.json
@@ -1,3 +1,12 @@
{
- "extends": "next/core-web-vitals"
+ "extends": [
+ "next/core-web-vitals",
+ "plugin:prettier/recommended"
+ ],
+ "rules": {
+ "prettier/prettier": "error",
+ "semi": "off",
+ "quotes": "off",
+ "indent": "off"
+ }
}
diff --git a/src/ClientApp/.prettierignore b/src/ClientApp/.prettierignore
new file mode 100644
index 0000000..d41778a
--- /dev/null
+++ b/src/ClientApp/.prettierignore
@@ -0,0 +1,25 @@
+# Ignore artifacts:
+build
+coverage
+dist
+node_modules
+
+# Ignore all configuration files:
+*.config.js
+*.config.ts
+
+# Ignore specific files:
+package-lock.json
+yarn.lock
+
+# Ignore logs:
+*.log
+
+# Ignore minified files:
+*.min.js
+
+# Ignore compiled code:
+*.d.ts
+
+# Ignore specific directories:
+public
diff --git a/src/ClientApp/.prettierrc b/src/ClientApp/.prettierrc
new file mode 100644
index 0000000..1618d7e
--- /dev/null
+++ b/src/ClientApp/.prettierrc
@@ -0,0 +1,7 @@
+{
+ "singleQuote": true,
+ "semi": false,
+ "tabWidth": 2,
+ "endOfLine": "lf",
+ "trailingComma": "none"
+}
diff --git a/src/ClientApp/.vscode/settings.json b/src/ClientApp/.vscode/settings.json
new file mode 100644
index 0000000..3ba6995
--- /dev/null
+++ b/src/ClientApp/.vscode/settings.json
@@ -0,0 +1,23 @@
+{
+ "editor.tabSize": 2,
+ // "editor.codeActionsOnSave": {
+ // "source.fixAll.eslint": true
+ // },
+ "[javascript]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[typescript]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[typescriptreact]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[json]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "eslint.validate": [
+ "javascript",
+ "typescript",
+ "typescriptreact"
+ ]
+}
diff --git a/src/ClientApp/ApiRoutes.tsx b/src/ClientApp/ApiRoutes.tsx
index 746524f..4c9c47e 100644
--- a/src/ClientApp/ApiRoutes.tsx
+++ b/src/ClientApp/ApiRoutes.tsx
@@ -1,17 +1,14 @@
enum ApiRoutes {
-
CACHE_ACCOUNTS = 'api/cache/accounts',
CACHE_ACCOUNT = 'api/cache/account/{accountId}',
CACHE_ACCOUNT_CONTACTS = 'api/cache/account/{accountId}/contacts',
CACHE_ACCOUNT_CONTACT = 'api/cache/account/{accountId}/contacts/{index}',
- CACHE_ACCOUNT_HOSTNAMES = 'api/cache/account/{accountId}/hostnames',
-
-
+ CACHE_ACCOUNT_HOSTNAMES = 'api/cache/account/{accountId}/hostnames'
// CERTS_FLOW_CONFIGURE_CLIENT = `api/CertsFlow/ConfigureClient`,
// CERTS_FLOW_TERMS_OF_SERVICE = `api/CertsFlow/TermsOfService/{sessionId}`,
// CERTS_FLOW_INIT = `api/CertsFlow/Init/{sessionId}/{accountId}`,
- // CERTS_FLOW_NEW_ORDER = `api/CertsFlow/NewOrder/{sessionId}`,
+ // CERTS_FLOW_NEW_ORDER = `api/CertsFlow/NewOrder/{sessionId}`,
// CERTS_FLOW_GET_ORDER = `api/CertsFlow/GetOrder/{sessionId}`,
// CERTS_FLOW_GET_CERTIFICATES = `api/CertsFlow/GetCertificates/{sessionId}`,
// CERTS_FLOW_APPLY_CERTIFICATES = `api/CertsFlow/ApplyCertificates/{sessionId}`,
@@ -19,15 +16,11 @@ enum ApiRoutes {
}
const GetApiRoute = (route: ApiRoutes, ...args: string[]): string => {
- let result: string = route;
- args.forEach(arg => {
- result = result.replace(/{.*?}/, arg);
- });
- return `http://localhost:5000/${result}`;
+ let result: string = route
+ args.forEach((arg) => {
+ result = result.replace(/{.*?}/, arg)
+ })
+ return `http://localhost:5000/${result}`
}
-
-export {
-GetApiRoute,
- ApiRoutes
-}
+export { GetApiRoute, ApiRoutes }
diff --git a/src/ClientApp/app/contact/page.tsx b/src/ClientApp/app/contact/page.tsx
index 3d8e37f..8019732 100644
--- a/src/ClientApp/app/contact/page.tsx
+++ b/src/ClientApp/app/contact/page.tsx
@@ -1,8 +1,9 @@
const ContactPage = () => {
- return (<>
-
Contact Us
- This is the contact page content.
- >
+ return (
+ <>
+ Contact Us
+ This is the contact page content.
+ >
)
}
diff --git a/src/ClientApp/app/functions/deepCopy.ts b/src/ClientApp/app/functions/deepCopy.ts
new file mode 100644
index 0000000..0269012
--- /dev/null
+++ b/src/ClientApp/app/functions/deepCopy.ts
@@ -0,0 +1,41 @@
+const deepCopy = (input: T): T => {
+ const map = new Map()
+
+ const clone = (item: any): any => {
+ if (item === null || typeof item !== 'object') {
+ return item
+ }
+
+ if (map.has(item)) {
+ return map.get(item)
+ }
+
+ let result: any
+
+ if (Array.isArray(item)) {
+ result = []
+ map.set(item, result)
+ item.forEach((element, index) => {
+ result[index] = clone(element)
+ })
+ } else if (item instanceof Date) {
+ result = new Date(item)
+ map.set(item, result)
+ } else if (item instanceof RegExp) {
+ result = new RegExp(item)
+ map.set(item, result)
+ } else {
+ result = Object.create(Object.getPrototypeOf(item))
+ map.set(item, result)
+ Object.keys(item).forEach((key) => {
+ result[key] = clone(item[key])
+ })
+ }
+
+ return result
+ }
+
+ return clone(input)
+}
+
+export { deepCopy }
diff --git a/src/ClientApp/app/functions/index.ts b/src/ClientApp/app/functions/index.ts
new file mode 100644
index 0000000..8d93905
--- /dev/null
+++ b/src/ClientApp/app/functions/index.ts
@@ -0,0 +1,3 @@
+import { deepCopy } from './deepCopy'
+
+export { deepCopy }
diff --git a/src/ClientApp/app/layout.tsx b/src/ClientApp/app/layout.tsx
index d359020..6e4971e 100644
--- a/src/ClientApp/app/layout.tsx
+++ b/src/ClientApp/app/layout.tsx
@@ -1,4 +1,4 @@
-"use client"
+'use client'
import React, { FC, useState, useEffect, useRef } from 'react'
import { SideMenu } from '@/components/sidemenu'
@@ -13,8 +13,8 @@ import { store } from '@/redux/store'
import './globals.css'
const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
+ title: 'Create Next App',
+ description: 'Generated by create next app'
}
const Layout: FC<{ children: React.ReactNode }> = ({ children }) => {
@@ -75,16 +75,16 @@ const Layout: FC<{ children: React.ReactNode }> = ({ children }) => {
-
-
+
+
-
- {children}
-
+ {children}
-
diff --git a/src/ClientApp/app/page.tsx b/src/ClientApp/app/page.tsx
index 8884e8a..429a59e 100644
--- a/src/ClientApp/app/page.tsx
+++ b/src/ClientApp/app/page.tsx
@@ -1,328 +1,416 @@
-"use client"
+'use client'
-import { ApiRoutes, GetApiRoute } from "@/ApiRoutes"
-import { httpService } from "@/services/httpService"
-import { FormEvent, useEffect, useRef, useState } from "react"
-import { useValidation, isValidEmail, isValidHostname } from "@/hooks/useValidation"
-import { CustomButton, CustomInput } from "@/controls"
-import { TrashIcon, PlusIcon } from "@heroicons/react/24/solid"
-import { GetAccountResponse } from "@/models/letsEncryptServer/cache/responses/GetAccountResponse"
-
-interface CacheAccountHostname {
- hostname: string
- expires: Date
- isUpcomingExpire: boolean
-}
-
-interface CacheAccount {
- accountId: string
- description?: string
- contacts: string[]
- hostnames: CacheAccountHostname[]
- isEditMode: boolean
-}
+import { ApiRoutes, GetApiRoute } from '@/ApiRoutes'
+import { httpService } from '@/services/httpService'
+import { FormEvent, useEffect, useRef, useState } from 'react'
+import {
+ useValidation,
+ isValidEmail,
+ isValidHostname
+} from '@/hooks/useValidation'
+import { CustomButton, CustomInput } from '@/controls'
+import { TrashIcon, PlusIcon } from '@heroicons/react/24/solid'
+import { GetAccountResponse } from '@/models/letsEncryptServer/cache/responses/GetAccountResponse'
+import { deepCopy } from './functions'
+import { CacheAccount } from '@/entities/CacheAccount'
export default function Page() {
- const [accounts, setAccounts] = useState([])
- const [initialAccounts, setInitialAccounts] = useState([])
+ const [accounts, setAccounts] = useState([])
+ const [initialAccounts, setInitialAccounts] = useState([])
- const {
- value: newContact,
- error: contactError,
- handleChange: handleContactChange
- } = useValidation({
- initialValue:"",
- validateFn: isValidEmail,
- errorMessage: "Invalid email format."
- })
- const {
- value: newHostname,
- error: hostnameError,
- handleChange: handleHostnameChange
- } = useValidation({
- initialValue: "",
- validateFn: isValidHostname,
- errorMessage: "Invalid hostname format."})
+ const {
+ value: newContact,
+ error: contactError,
+ handleChange: handleContactChange
+ } = useValidation({
+ initialValue: '',
+ validateFn: isValidEmail,
+ errorMessage: 'Invalid email format.'
+ })
+ const {
+ value: newHostname,
+ error: hostnameError,
+ handleChange: handleHostnameChange
+ } = useValidation({
+ initialValue: '',
+ validateFn: isValidHostname,
+ errorMessage: 'Invalid hostname format.'
+ })
- const init = useRef(false)
+ const init = useRef(false)
- useEffect(() => {
- if (init.current) return
+ useEffect(() => {
+ if (init.current) return
+ console.log('Fetching accounts')
- console.log("Fetching accounts")
+ const fetchAccounts = async () => {
+ const newAccounts: CacheAccount[] = []
+ const accounts = await httpService.get(
+ GetApiRoute(ApiRoutes.CACHE_ACCOUNTS)
+ )
- const fetchAccounts = async () => {
- const newAccounts: CacheAccount[] = []
- const accounts = await httpService.get(GetApiRoute(ApiRoutes.CACHE_ACCOUNTS))
+ accounts?.forEach((account) => {
+ newAccounts.push({
+ accountId: account.accountId,
+ contacts: account.contacts,
+ hostnames: account.hostnames.map((h) => ({
+ hostname: h.hostname,
+ expires: new Date(h.expires),
+ isUpcomingExpire: h.isUpcomingExpire
+ })),
+ isEditMode: false
+ })
+ })
- accounts?.forEach((account) => {
- newAccounts.push({
- accountId: account.accountId,
- contacts: account.contacts,
- hostnames: account.hostnames.map(h => ({
- hostname: h.hostname,
- expires: new Date(h.expires),
- isUpcomingExpire: h.isUpcomingExpire
- })),
- isEditMode: false
- })
- });
-
- setAccounts(newAccounts)
- setInitialAccounts(JSON.parse(JSON.stringify(newAccounts))) // Clone initial state
- }
-
- fetchAccounts()
- init.current = true
- }, [])
-
- const toggleEditMode = (accountId: string) => {
- setAccounts(accounts.map(account =>
- account.accountId === accountId ? { ...account, isEditMode: !account.isEditMode } : account
- ))
+ setAccounts(newAccounts)
+ setInitialAccounts(deepCopy(newAccounts)) // Clone initial state
}
- const deleteAccount = (accountId: string) => {
- setAccounts(accounts.filter(account => account.accountId !== accountId))
+ fetchAccounts()
+ init.current = true
+ }, [])
- // TODO: Revoke all certificates
- // TODO: Remove from cache
- }
-
- const deleteContact = (accountId: string, contact: string) => {
- const account = accounts.find(account => account.accountId === accountId)
- if (account?.contacts.length ?? 0 < 1) return
-
- // TODO: Remove from cache
- httpService.delete(GetApiRoute(ApiRoutes.CACHE_ACCOUNT_CONTACT, accountId, contact))
-
- setAccounts(accounts.map(account =>
- account.accountId === accountId
- ? { ...account, contacts: account.contacts.filter(c => c !== contact) }
- : account
- ))
- }
-
- const addContact = (accountId: string) => {
- if (newContact.trim() === "" || contactError) {
- return
- }
-
- if (accounts.find(account => account.accountId === accountId)?.contacts.includes(newContact.trim()))
- return
-
- setAccounts(accounts.map(account =>
- account.accountId === accountId
- ? { ...account, contacts: [...account.contacts, newContact.trim()] }
- : account
- ))
- handleContactChange("")
- }
-
- const deleteHostname = (accountId: string, hostname: string) => {
- const account = accounts.find(account => account.accountId === accountId)
- if (account?.hostnames.length ?? 0 < 1) return
-
- // TODO: Revoke certificate
- // TODO: Remove from cache
-
- setAccounts(accounts.map(account =>
- account.accountId === accountId
- ? { ...account, hostnames: account.hostnames.filter(h => h.hostname !== hostname) }
- : account
- ))
- }
-
- const addHostname = (accountId: string) => {
- if (newHostname.trim() === "" || hostnameError) {
- return
- }
-
- if (accounts.find(account => account.accountId === accountId)?.hostnames.some(h => h.hostname === newHostname.trim()))
- return
-
- setAccounts(accounts.map(account =>
- account.accountId === accountId
- ? { ...account, hostnames: [...account.hostnames, { hostname: newHostname.trim(), expires: new Date(), isUpcomingExpire: false }] }
- : account
- ))
- handleHostnameChange("")
- }
-
- const handleSubmit = async (e: FormEvent, accountId: string) => {
- e.preventDefault()
-
- const account = accounts.find(acc => acc.accountId === accountId)
- const initialAccount = initialAccounts.find(acc => acc.accountId === accountId)
-
- if (!account || !initialAccount) return
-
- const contactChanges = {
- added: account.contacts.filter(contact => !initialAccount.contacts.includes(contact)),
- removed: initialAccount.contacts.filter(contact => !account.contacts.includes(contact))
- }
-
- const hostnameChanges = {
- added: account.hostnames.filter(hostname => !initialAccount.hostnames.some(h => h.hostname === hostname.hostname)),
- removed: initialAccount.hostnames.filter(hostname => !account.hostnames.some(h => h.hostname === hostname.hostname))
- }
-
- // Handle contact changes
- if (contactChanges.added.length > 0) {
- // TODO: POST new contacts
- console.log("Added contacts:", contactChanges.added)
- }
- if (contactChanges.removed.length > 0) {
- // TODO: DELETE removed contacts
- console.log("Removed contacts:", contactChanges.removed)
- }
-
- // Handle hostname changes
- if (hostnameChanges.added.length > 0) {
- // TODO: POST new hostnames
- console.log("Added hostnames:", hostnameChanges.added)
- }
- if (hostnameChanges.removed.length > 0) {
- // TODO: DELETE removed hostnames
- console.log("Removed hostnames:", hostnameChanges.removed)
- }
-
- // Save current state as initial state
- setInitialAccounts(JSON.parse(JSON.stringify(accounts)))
- toggleEditMode(accountId)
- }
-
- return (
-
-
LetsEncrypt Auto Renew
- {
- accounts.map(account => (
-
-
-
Account: {account.accountId}
- toggleEditMode(account.accountId)} className="bg-blue-500 text-white px-3 py-1 rounded">
- {account.isEditMode ? "View Mode" : "Edit Mode"}
-
-
- {account.isEditMode ? (
-
- ) : (
- <>
-
-
Description:
-
-
-
Contacts:
-
- {
- account.contacts.map(contact => (
- -
- {contact}
-
- ))
- }
-
-
-
-
Hostnames:
-
- {
- account.hostnames.map(hostname => (
- -
- {hostname.hostname} - {hostname.expires.toDateString()} -
-
- {hostname.isUpcomingExpire ? 'Upcoming' : 'Not Upcoming'}
-
-
- ))
- }
-
-
- >
- )}
-
- ))
- }
-
+ const toggleEditMode = (accountId: string) => {
+ setAccounts(
+ accounts.map((account) =>
+ account.accountId === accountId
+ ? { ...account, isEditMode: !account.isEditMode }
+ : account
+ )
)
+ }
+
+ const deleteAccount = (accountId: string) => {
+ setAccounts(accounts.filter((account) => account.accountId !== accountId))
+
+ // TODO: Revoke all certificates
+ // TODO: Remove from cache
+ }
+
+ const deleteContact = (accountId: string, contact: string) => {
+ const account = accounts.find((account) => account.accountId === accountId)
+ if (account?.contacts.length ?? 0 < 1) return
+
+ // TODO: Remove from cache
+ httpService.delete(
+ GetApiRoute(ApiRoutes.CACHE_ACCOUNT_CONTACT, accountId, contact)
+ )
+
+ setAccounts(
+ accounts.map((account) =>
+ account.accountId === accountId
+ ? {
+ ...account,
+ contacts: account.contacts.filter((c) => c !== contact)
+ }
+ : account
+ )
+ )
+ }
+
+ const addContact = (accountId: string) => {
+ if (newContact.trim() === '' || contactError) {
+ return
+ }
+
+ if (
+ accounts
+ .find((account) => account.accountId === accountId)
+ ?.contacts.includes(newContact.trim())
+ )
+ return
+
+ setAccounts(
+ accounts.map((account) =>
+ account.accountId === accountId
+ ? { ...account, contacts: [...account.contacts, newContact.trim()] }
+ : account
+ )
+ )
+ handleContactChange('')
+ }
+
+ const deleteHostname = (accountId: string, hostname: string) => {
+ const account = accounts.find((account) => account.accountId === accountId)
+ if (account?.hostnames.length ?? 0 < 1) return
+
+ // TODO: Revoke certificate
+ // TODO: Remove from cache
+
+ setAccounts(
+ accounts.map((account) =>
+ account.accountId === accountId
+ ? {
+ ...account,
+ hostnames: account.hostnames.filter(
+ (h) => h.hostname !== hostname
+ )
+ }
+ : account
+ )
+ )
+ }
+
+ const addHostname = (accountId: string) => {
+ if (newHostname.trim() === '' || hostnameError) {
+ return
+ }
+
+ if (
+ accounts
+ .find((account) => account.accountId === accountId)
+ ?.hostnames.some((h) => h.hostname === newHostname.trim())
+ )
+ return
+
+ setAccounts(
+ accounts.map((account) =>
+ account.accountId === accountId
+ ? {
+ ...account,
+ hostnames: [
+ ...account.hostnames,
+ {
+ hostname: newHostname.trim(),
+ expires: new Date(),
+ isUpcomingExpire: false
+ }
+ ]
+ }
+ : account
+ )
+ )
+ handleHostnameChange('')
+ }
+
+ const handleSubmit = async (
+ e: FormEvent,
+ accountId: string
+ ) => {
+ e.preventDefault()
+
+ const account = accounts.find((acc) => acc.accountId === accountId)
+ const initialAccount = initialAccounts.find(
+ (acc) => acc.accountId === accountId
+ )
+
+ if (!account || !initialAccount) return
+
+ const contactChanges = {
+ added: account.contacts.filter(
+ (contact) => !initialAccount.contacts.includes(contact)
+ ),
+ removed: initialAccount.contacts.filter(
+ (contact) => !account.contacts.includes(contact)
+ )
+ }
+
+ const hostnameChanges = {
+ added: account.hostnames.filter(
+ (hostname) =>
+ !initialAccount.hostnames.some(
+ (h) => h.hostname === hostname.hostname
+ )
+ ),
+ removed: initialAccount.hostnames.filter(
+ (hostname) =>
+ !account.hostnames.some((h) => h.hostname === hostname.hostname)
+ )
+ }
+
+ // Handle contact changes
+ if (contactChanges.added.length > 0) {
+ // TODO: POST new contacts
+ console.log('Added contacts:', contactChanges.added)
+ }
+ if (contactChanges.removed.length > 0) {
+ // TODO: DELETE removed contacts
+ console.log('Removed contacts:', contactChanges.removed)
+ }
+
+ // Handle hostname changes
+ if (hostnameChanges.added.length > 0) {
+ // TODO: POST new hostnames
+ console.log('Added hostnames:', hostnameChanges.added)
+ }
+ if (hostnameChanges.removed.length > 0) {
+ // TODO: DELETE removed hostnames
+ console.log('Removed hostnames:', hostnameChanges.removed)
+ }
+
+ // Save current state as initial state
+ setInitialAccounts(deepCopy(accounts))
+ toggleEditMode(accountId)
+ }
+
+ return (
+
+
+ LetsEncrypt Auto Renew
+
+ {accounts.map((account) => (
+
+
+
+ Account: {account.accountId}
+
+ toggleEditMode(account.accountId)}
+ className="bg-blue-500 text-white px-3 py-1 rounded"
+ >
+ {account.isEditMode ? 'View Mode' : 'Edit Mode'}
+
+
+ {account.isEditMode ? (
+
+ ) : (
+ <>
+
+
Description:
+
+
+
Contacts:
+
+ {account.contacts.map((contact) => (
+ -
+ {contact}
+
+ ))}
+
+
+
+
Hostnames:
+
+ {account.hostnames.map((hostname) => (
+ -
+ {hostname.hostname} - {hostname.expires.toDateString()} -
+
+ {hostname.isUpcomingExpire
+ ? 'Upcoming'
+ : 'Not Upcoming'}
+
+
+ ))}
+
+
+ >
+ )}
+
+ ))}
+
+ )
}
diff --git a/src/ClientApp/app/register/page.tsx b/src/ClientApp/app/register/page.tsx
index 65c94f0..795b065 100644
--- a/src/ClientApp/app/register/page.tsx
+++ b/src/ClientApp/app/register/page.tsx
@@ -1,209 +1,233 @@
-"use client"
+'use client'
-import { ApiRoutes, GetApiRoute } from "@/ApiRoutes"
-import { httpService } from "@/services/httpService"
-import { FormEvent, useEffect, useRef, useState } from "react"
-import { useValidation, isValidContact, isValidHostname } from "@/hooks/useValidation"
-import { CustomButton, CustomInput } from "@/controls"
-import { FaTrash, FaPlus } from "react-icons/fa"
-import { GetAccountResponse } from "@/models/letsEncryptServer/cache/responses/GetAccountResponse"
-
-interface CacheAccountHostname {
- hostname: string
- expires: Date
- isUpcomingExpire: boolean
-}
+import { ApiRoutes, GetApiRoute } from '@/ApiRoutes'
+import { httpService } from '@/services/httpService'
+import { FormEvent, useEffect, useRef, useState } from 'react'
+import {
+ useValidation,
+ isValidContact,
+ isValidHostname
+} from '@/hooks/useValidation'
+import { CustomButton, CustomInput } from '@/controls'
+import { FaTrash, FaPlus } from 'react-icons/fa'
+import { GetAccountResponse } from '@/models/letsEncryptServer/cache/responses/GetAccountResponse'
+import { deepCopy } from '../functions'
interface CacheAccount {
- accountId: string
- description?: string
- contacts: string[]
- hostnames: CacheAccountHostname[]
- isEditMode: boolean
+ description?: string
+ contacts: string[]
+ hostnames: string[]
}
const RegisterPage = () => {
- const [accounts, setAccounts] = useState([])
- const [initialAccounts, setInitialAccounts] = useState([])
- const [description, setDescription] = useState("")
- const [contacts, setContacts] = useState([])
- const [hostnames, setHostnames] = useState([])
+ const [account, setAccount] = useState(null)
- const {
- value: newContact,
- error: contactError,
- handleChange: handleContactChange,
- reset: resetContact
- } = useValidation({
- initialValue: "",
- validateFn: isValidContact,
- errorMessage: "Invalid contact. Must be a valid email or phone number."
+ const {
+ value: newContact,
+ error: contactError,
+ handleChange: handleContactChange,
+ reset: resetContact
+ } = useValidation({
+ initialValue: '',
+ validateFn: isValidContact,
+ errorMessage: 'Invalid contact. Must be a valid email or phone number.'
+ })
+
+ const {
+ value: newHostname,
+ error: hostnameError,
+ handleChange: handleHostnameChange,
+ reset: resetHostname
+ } = useValidation({
+ initialValue: '',
+ validateFn: isValidHostname,
+ errorMessage: 'Invalid hostname format.'
+ })
+
+ const init = useRef(false)
+
+ useEffect(() => {
+ if (init.current) return
+
+ init.current = true
+ }, [])
+
+ const handleDescription = (description: string) => {}
+
+ const handleAddContact = () => {
+ if (newContact !== '' || contactError) return
+
+ setAccount((prev) => {
+ const newAccount: CacheAccount =
+ prev !== null
+ ? deepCopy(prev)
+ : {
+ contacts: [],
+ hostnames: []
+ }
+
+ newAccount.contacts.push(newContact)
+
+ return newAccount
})
- const {
- value: newHostname,
- error: hostnameError,
- handleChange: handleHostnameChange,
- reset: resetHostname
- } = useValidation({
- initialValue: "",
- validateFn: isValidHostname,
- errorMessage: "Invalid hostname format."
+ resetContact()
+ }
+
+ const handleAddHostname = () => {
+ if (newHostname !== '' || hostnameError) return
+
+ setAccount((prev) => {
+ const newAccount: CacheAccount =
+ prev !== null
+ ? deepCopy(prev)
+ : {
+ contacts: [],
+ hostnames: []
+ }
+
+ newAccount.hostnames.push(newHostname)
+
+ return newAccount
})
- const init = useRef(false)
+ resetHostname()
+ }
- useEffect(() => {
- if (init.current) return
+ const handleDeleteContact = (contact: string) => {
+ setAccount((prev) => {
+ if (prev === null) return null
- const fetchAccounts = async () => {
- const newAccounts: CacheAccount[] = []
- const accounts = await httpService.get(GetApiRoute(ApiRoutes.CACHE_ACCOUNTS))
+ const newAccount = deepCopy(prev)
+ newAccount.contacts = newAccount.contacts.filter((c) => c !== contact)
- accounts?.forEach((account) => {
- newAccounts.push({
- accountId: account.accountId,
- contacts: account.contacts,
- hostnames: account.hostnames.map(h => ({
- hostname: h.hostname,
- expires: new Date(h.expires),
- isUpcomingExpire: h.isUpcomingExpire
- })),
- isEditMode: false
- })
- })
+ return newAccount
+ })
+ }
- setAccounts(newAccounts)
- setInitialAccounts(JSON.parse(JSON.stringify(newAccounts))) // Clone initial state
- }
+ const handleDeleteHostname = (hostname: string) => {
+ setAccount((prev) => {
+ if (prev === null) return null
- fetchAccounts()
- init.current = true
- }, [])
+ const newAccount = deepCopy(prev)
+ newAccount.hostnames = newAccount.hostnames.filter((h) => h !== hostname)
- const handleAddContact = () => {
- if (newContact.trim() !== "" && !contactError) {
- setContacts([...contacts, newContact.trim()])
- resetContact()
- }
- }
+ return newAccount
+ })
+ }
- const handleAddHostname = () => {
- if (newHostname.trim() !== "" && !hostnameError) {
- setHostnames([...hostnames, newHostname.trim()])
- resetHostname()
- }
- }
+ const handleSubmit = async (e: FormEvent) => {
+ e.preventDefault()
- const handleDeleteContact = (contact: string) => {
- setContacts(contacts.filter(c => c !== contact))
- }
+ console.log(account)
+ }
- const handleDeleteHostname = (hostname: string) => {
- setHostnames(hostnames.filter(h => h !== hostname))
- }
-
- const handleSubmit = async (e: FormEvent) => {
- e.preventDefault()
-
- if (!description || contacts.length === 0 || hostnames.length === 0) {
- return
- }
-
- const newAccount = {
- description,
- contacts,
- hostnames: hostnames.map(hostname => ({ hostname, expires: new Date(), isUpcomingExpire: false }))
- }
-
- // TODO: Implement API call to create new account
- console.log("New account data:", newAccount)
-
- // Reset form fields
- setDescription("")
- setContacts([])
- setHostnames([])
- }
-
- return (
-
-
Register LetsEncrypt Account
-
+ return (
+
+
+ Register LetsEncrypt Account
+
+
+
+ )
}
export default RegisterPage
diff --git a/src/ClientApp/components/footer.tsx b/src/ClientApp/components/footer.tsx
index 48ce615..611ed6f 100644
--- a/src/ClientApp/components/footer.tsx
+++ b/src/ClientApp/components/footer.tsx
@@ -1,12 +1,10 @@
import React from 'react'
-
interface FooterProps {
className?: string
}
const Footer = (props: FooterProps) => {
-
const { className } = props
return (