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 ? ( -
handleSubmit(e, account.accountId)}> -
-

Description:

-
-
-

Contacts:

-
    - { - account.contacts.map(contact => ( -
  • - {contact} - -
  • - )) - } -
-
- - -
-
-
-

Hostnames:

-
    - { - account.hostnames.map(hostname => ( -
  • -
    - {hostname.hostname} - {hostname.expires.toDateString()} - - - {hostname.isUpcomingExpire ? 'Upcoming' : 'Not Upcoming'} - -
    - -
  • - )) - } -
-
- - -
-
-
- - - Submit - -
-
- ) : ( - <> -
-

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 ? ( +
handleSubmit(e, account.accountId)}> +
+

Description:

+
+
+

Contacts:

+
    + {account.contacts.map((contact) => ( +
  • + {contact} + +
  • + ))} +
+
+ + +
+
+
+

Hostnames:

+
    + {account.hostnames.map((hostname) => ( +
  • +
    + {hostname.hostname} - {hostname.expires.toDateString()}{' '} + - + + {hostname.isUpcomingExpire + ? 'Upcoming' + : 'Not Upcoming'} + +
    + +
  • + ))} +
+
+ + +
+
+
+ + + Submit + +
+
+ ) : ( + <> +
+

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

-
-
- setDescription(e.target.value)} - placeholder="Account Description" - title="Description" - inputClassName="border p-2 rounded w-full" - className="mb-4" - /> -
-
-

Contacts:

-
    - {contacts.map(contact => ( -
  • - {contact} - -
  • - ))} -
-
- - -
-
-
-

Hostnames:

-
    - {hostnames.map(hostname => ( -
  • - {hostname} - -
  • - ))} -
-
- - -
-
- - Create Account - -
+ return ( +
+

+ Register LetsEncrypt Account +

+
+
+
- ) +
+

Contacts:

+
    + {account?.contacts.map((contact) => ( +
  • + {contact} + +
  • + ))} +
+
+ + +
+
+
+

Hostnames:

+
    + {account?.hostnames.map((hostname) => ( +
  • + {hostname} + +
  • + ))} +
+
+ + +
+
+ + Create 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 (