Skip to content

Commit 21a15d9

Browse files
committed
Territory Edit UI/UX changes
Copy - Uses CopyButton from Form to copy DNS record values Domain Verification UI/UX - Can trigger a re-verification by re-submitting the domain - slight cleanup Validation hints = Better validation hint
1 parent eba5d09 commit 21a15d9

File tree

4 files changed

+114
-29
lines changed

4 files changed

+114
-29
lines changed

components/form.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export function SubmitButton ({
7878
)
7979
}
8080

81-
function CopyButton ({ value, icon, ...props }) {
81+
export function CopyButton ({ value, icon, append, ...props }) {
8282
const toaster = useToast()
8383
const [copied, setCopied] = useState(false)
8484

@@ -101,6 +101,14 @@ function CopyButton ({ value, icon, ...props }) {
101101
)
102102
}
103103

104+
if (append) {
105+
return (
106+
<span className={styles.appendButton} {...props} onClick={handleClick}>
107+
{append}
108+
</span>
109+
)
110+
}
111+
104112
return (
105113
<Button className={styles.appendButton} {...props} onClick={handleClick}>
106114
{copied ? <Thumb width={18} height={18} /> : 'copy'}

components/item.module.css

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,3 +247,37 @@ a.link:visited {
247247
.skeleton .otherItemLonger {
248248
width: 60px;
249249
}
250+
251+
.record {
252+
display: flex;
253+
flex-direction: column;
254+
margin-right: 0.5rem;
255+
}
256+
257+
.clipboard {
258+
cursor: pointer;
259+
fill: var(--theme-grey);
260+
width: 1rem;
261+
height: 1rem;
262+
opacity: 0;
263+
transition: opacity 0.1s ease, fill 0.1s ease;
264+
}
265+
266+
.record:hover .clipboard {
267+
opacity: 1;
268+
}
269+
270+
.clipboard:hover {
271+
fill: var(--bs-primary);
272+
}
273+
274+
.refresh {
275+
cursor: pointer;
276+
fill: var(--theme-grey);
277+
width: 1rem;
278+
height: 1rem;
279+
}
280+
281+
.refresh:hover {
282+
fill: var(--bs-primary);
283+
}

components/territory-domains.js

Lines changed: 70 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Badge } from 'react-bootstrap'
2-
import { Form, Input, SubmitButton } from './form'
2+
import { Form, Input, SubmitButton, CopyButton } from './form'
33
import { useMutation, useQuery } from '@apollo/client'
44
import { customDomainSchema } from '@/lib/validate'
55
import ActionTooltip from './action-tooltip'
@@ -12,6 +12,9 @@ import { signIn } from 'next-auth/react'
1212
import BrandingForm from '@/components/territory-branding-form'
1313
import Head from 'next/head'
1414
import Moon from '@/svgs/moon-fill.svg'
15+
import ClipboardLine from '@/svgs/clipboard-line.svg'
16+
import RefreshLine from '@/svgs/refresh-line.svg'
17+
import styles from './item.module.css'
1518

1619
// Domain context for custom domains
1720
const DomainContext = createContext({
@@ -91,19 +94,27 @@ const getSSLStatusBadge = (status) => {
9194
}
9295
}
9396

94-
export function DomainLabel ({ customDomain, polling }) {
97+
const DomainLabel = ({ customDomain, polling }) => {
9598
const { domain, status, verification, lastVerifiedAt } = customDomain || {}
99+
96100
return (
97101
<div className='d-flex align-items-center gap-2'>
98102
<span>custom domain</span>
99103
{domain && (
100104
<ActionTooltip overlayText={lastVerifiedAt ? new Date(lastVerifiedAt).toLocaleString() : ''}>
101105
<div className='d-flex align-items-center gap-2'>
102-
{status !== 'HOLD' && (
103-
<>
104-
{getStatusBadge(verification?.dns?.state)}
105-
{getSSLStatusBadge(verification?.ssl?.state)}
106-
</>
106+
{status !== 'HOLD'
107+
? (
108+
<>
109+
{getStatusBadge(verification?.dns?.state)}
110+
{getSSLStatusBadge(verification?.ssl?.state)}
111+
</>
112+
)
113+
: (<Badge bg='secondary'>HOLD</Badge>)}
114+
{status === 'HOLD' && (
115+
<SubmitButton variant='link' className='p-0'>
116+
<RefreshLine className={styles.refresh} style={{ width: '1rem', height: '1rem' }} />
117+
</SubmitButton>
107118
)}
108119
{polling && <Moon className='spin fill-grey' style={{ width: '1rem', height: '1rem' }} />}
109120
</div>
@@ -113,38 +124,66 @@ export function DomainLabel ({ customDomain, polling }) {
113124
)
114125
}
115126

116-
export function DomainGuidelines ({ customDomain }) {
127+
const DomainGuidelines = ({ customDomain }) => {
117128
const { domain, verification } = customDomain || {}
129+
130+
const dnsRecord = (host, value) => (
131+
<div className='d-flex align-items-center gap-2'>
132+
<span className={`${styles.record}`}>
133+
<small className='fw-bold text-muted d-flex align-items-center gap-1 position-relative'>
134+
host
135+
<CopyButton
136+
value={host}
137+
append={
138+
<ClipboardLine
139+
className={`${styles.clipboard}`}
140+
style={{ width: '1rem', height: '1rem' }}
141+
/>
142+
}
143+
/>
144+
</small>
145+
<pre>{host}</pre>
146+
</span>
147+
<span className={`${styles.record}`}>
148+
<small className='fw-bold text-muted d-flex align-items-center gap-1 position-relative'>
149+
value
150+
<CopyButton
151+
value={value}
152+
append={
153+
<ClipboardLine
154+
className={`${styles.clipboard}`}
155+
style={{ width: '1rem', height: '1rem' }}
156+
/>
157+
}
158+
/>
159+
</small>
160+
<pre>{value}</pre>
161+
</span>
162+
</div>
163+
)
164+
118165
return (
119-
<>
166+
<div className='d-flex'>
120167
{(verification?.dns?.state && verification?.dns?.state !== 'VERIFIED') && (
121-
<>
168+
<div className='d-flex flex-column gap-2'>
122169
<h5>Step 1: Verify your domain</h5>
123170
<p>Add the following DNS records to verify ownership of your domain:</p>
124171
<h6>CNAME</h6>
125-
<p>
126-
Host: <pre>{domain || 'www'}</pre>
127-
Value: <pre>stacker.news</pre>
128-
</p>
172+
{dnsRecord(domain || 'www', verification?.dns?.cname)}
173+
<hr />
129174
<h6>TXT</h6>
130-
<p>
131-
Host: <pre>{domain || 'www'}</pre>
132-
Value: <pre>{verification?.dns?.txt}</pre>
133-
</p>
134-
</>
175+
{dnsRecord(domain || 'www', verification?.dns?.txt)}
176+
</div>
135177
)}
136178
{verification?.ssl?.state === 'PENDING' && (
137-
<>
179+
<div className=''>
138180
<h5>Step 2: Prepare your domain for SSL</h5>
139181
<p>We issued an SSL certificate for your domain. To validate it, add the following CNAME record:</p>
140182
<h6>CNAME</h6>
141-
<p>
142-
Host: <pre>{verification?.ssl?.cname || 'waiting for SSL certificate'}</pre>
143-
Value: <pre>{verification?.ssl?.value || 'waiting for SSL certificate'}</pre>
144-
</p>
145-
</>
183+
{dnsRecord(verification?.ssl?.cname || 'waiting for SSL certificate', verification?.ssl?.value || 'waiting for SSL certificate')}
184+
</div>
146185
)}
147-
</>
186+
</div>
148187
)
149188
}
150189

@@ -179,7 +218,11 @@ export default function CustomDomainForm ({ sub }) {
179218
}
180219
})
181220
refetch()
182-
toaster.success('domain updated successfully')
221+
if (domain) {
222+
toaster.success('started domain verification')
223+
} else {
224+
toaster.success('domain removed successfully')
225+
}
183226
} catch (error) {
184227
toaster.danger('failed to update domain', { error })
185228
}

lib/validate.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ export function territoryTransferSchema ({ me, ...args }) {
360360
export function customDomainSchema (args) {
361361
return object({
362362
domain: string().matches(/^(?:[a-z0-9-]+\.){2,}[a-z]{2,}$/, {
363-
message: 'enter a valid domain name (e.g., www.example.com)'
363+
message: 'CNAME records only support subdomains (e.g., www.example.com, sub.example.com)'
364364
}).nullable()
365365
})
366366
}

0 commit comments

Comments
 (0)