|
1 | 1 | import type React from 'react'; |
2 | 2 | import { useState } from 'react'; |
3 | 3 |
|
4 | | -import { Box, Button, Col, descriptors, Flex, Flow, localizationKeys, useLocalizations } from '@/customizables'; |
| 4 | +import { |
| 5 | + Box, |
| 6 | + Button, |
| 7 | + Col, |
| 8 | + descriptors, |
| 9 | + Flex, |
| 10 | + Flow, |
| 11 | + Icon, |
| 12 | + localizationKeys, |
| 13 | + Spinner, |
| 14 | + Table, |
| 15 | + Tbody, |
| 16 | + Td, |
| 17 | + Text, |
| 18 | + Th, |
| 19 | + Thead, |
| 20 | + Tr, |
| 21 | + useLocalizations, |
| 22 | +} from '@/customizables'; |
5 | 23 | import { Field } from '@/elements/FieldControl'; |
6 | 24 | import { Form } from '@/elements/Form'; |
7 | 25 | import { TagPill } from '@/elements/TagInput'; |
| 26 | +import { useClipboard } from '@/hooks'; |
| 27 | +import { Checkmark, Clipboard } from '@/icons'; |
8 | 28 | import { useFormControl } from '@/utils/useFormControl'; |
9 | 29 |
|
10 | 30 | import { Step } from '../elements/Step'; |
@@ -33,30 +53,46 @@ export const VerifyDomainStep = (): JSX.Element => { |
33 | 53 | </Wizard> |
34 | 54 |
|
35 | 55 | <Step.Body> |
36 | | - <Step.Section> |
37 | | - {/* TODO -> Trigger mutation to add domain */} |
38 | | - <DomainsField |
39 | | - onSubmit={domain => { |
40 | | - setDomains(prev => [...prev, domain]); |
41 | | - }} |
42 | | - /> |
| 56 | + <Step.Section sx={t => ({ gap: t.space.$5 })}> |
| 57 | + <Col> |
| 58 | + {/* TODO -> Trigger mutation to add domain */} |
| 59 | + <DomainsField |
| 60 | + onSubmit={domain => { |
| 61 | + setDomains(prev => [...prev, domain]); |
| 62 | + }} |
| 63 | + /> |
43 | 64 |
|
44 | | - {domains.length > 0 && ( |
45 | | - <Flex |
46 | | - wrap='wrap' |
47 | | - sx={t => ({ gap: t.space.$2, marginTop: t.space.$4 })} |
48 | | - > |
49 | | - {domains.map(domain => ( |
50 | | - <TagPill |
51 | | - key={domain} |
52 | | - /* TODO -> Trigger mutation to remove domain */ |
53 | | - onRemoveClick={() => setDomains(prev => prev.filter(d => d !== domain))} |
54 | | - > |
55 | | - {domain} |
56 | | - </TagPill> |
57 | | - ))} |
58 | | - </Flex> |
59 | | - )} |
| 65 | + {domains.length > 0 && ( |
| 66 | + <Flex |
| 67 | + wrap='wrap' |
| 68 | + sx={t => ({ gap: t.space.$2, marginTop: t.space.$4 })} |
| 69 | + > |
| 70 | + {domains.map(domain => ( |
| 71 | + <TagPill |
| 72 | + key={domain} |
| 73 | + /* TODO -> Trigger mutation to remove domain */ |
| 74 | + onRemoveClick={() => setDomains(prev => prev.filter(d => d !== domain))} |
| 75 | + > |
| 76 | + {domain} |
| 77 | + </TagPill> |
| 78 | + ))} |
| 79 | + </Flex> |
| 80 | + )} |
| 81 | + </Col> |
| 82 | + |
| 83 | + {/* TODO -> Only render when there is existing organization domains */} |
| 84 | + <Col sx={t => ({ gap: t.space.$3 })}> |
| 85 | + <Col sx={t => ({ gap: t.space.$4 })}> |
| 86 | + <Text |
| 87 | + localizationKey={localizationKeys('configureSSO.verifyDomainStep.txtRecordInstructions.paragraph1')} |
| 88 | + /> |
| 89 | + <Text |
| 90 | + localizationKey={localizationKeys('configureSSO.verifyDomainStep.txtRecordInstructions.paragraph2')} |
| 91 | + /> |
| 92 | + </Col> |
| 93 | + |
| 94 | + <TxtRecordTable /> |
| 95 | + </Col> |
60 | 96 | </Step.Section> |
61 | 97 | </Step.Body> |
62 | 98 | </Step> |
@@ -127,3 +163,111 @@ const DomainsField = ({ onSubmit }: { onSubmit: (domain: string) => void }): JSX |
127 | 163 | */ |
128 | 164 | const DOMAIN_REGEX = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/i; |
129 | 165 | const isValidDomain = (value: string): boolean => DOMAIN_REGEX.test(value); |
| 166 | + |
| 167 | +const txtRecordsMock = [ |
| 168 | + { |
| 169 | + domain: 'test.com', |
| 170 | + type: 'TXT', |
| 171 | + name: '@', |
| 172 | + value: 'hellooo-domain-verification-axxn86=sdfsdfsdfsdfdsf', |
| 173 | + status: 'pending', |
| 174 | + }, |
| 175 | + { |
| 176 | + domain: 'acme.com', |
| 177 | + type: 'TXT', |
| 178 | + name: '@', |
| 179 | + value: 'hellooo2-domain-verification-axxn86=sdfsdfsdfsdfdsf', |
| 180 | + status: 'verified', |
| 181 | + }, |
| 182 | +]; |
| 183 | + |
| 184 | +const TxtRecordTable = (): JSX.Element => { |
| 185 | + return ( |
| 186 | + <Table> |
| 187 | + <Thead> |
| 188 | + <Tr> |
| 189 | + <Th>Domain</Th> |
| 190 | + <Th>Type</Th> |
| 191 | + <Th>Host / Name</Th> |
| 192 | + <Th>Value</Th> |
| 193 | + </Tr> |
| 194 | + </Thead> |
| 195 | + <Tbody> |
| 196 | + {txtRecordsMock.map(record => ( |
| 197 | + <Tr key={record.domain}> |
| 198 | + <Td> |
| 199 | + <Flex |
| 200 | + as='span' |
| 201 | + align='center' |
| 202 | + sx={t => ({ gap: t.space.$2 })} |
| 203 | + > |
| 204 | + {record.status === 'verified' ? ( |
| 205 | + <Icon |
| 206 | + icon={Checkmark} |
| 207 | + colorScheme='success' |
| 208 | + sx={t => ({ flexShrink: 0, width: t.sizes.$3, height: t.sizes.$3 })} |
| 209 | + /> |
| 210 | + ) : ( |
| 211 | + <Spinner |
| 212 | + size='xs' |
| 213 | + colorScheme='neutral' |
| 214 | + sx={{ flexShrink: 0 }} |
| 215 | + /> |
| 216 | + )} |
| 217 | + <Text as='span'>{record.domain}</Text> |
| 218 | + </Flex> |
| 219 | + </Td> |
| 220 | + <Td> |
| 221 | + <Text as='span'>{record.type}</Text> |
| 222 | + </Td> |
| 223 | + <Td> |
| 224 | + <Text as='span'>{record.name}</Text> |
| 225 | + </Td> |
| 226 | + <Td sx={{ width: '50%', maxWidth: '1px' }}> |
| 227 | + <TxtRecordValueCell value={record.value} /> |
| 228 | + </Td> |
| 229 | + </Tr> |
| 230 | + ))} |
| 231 | + </Tbody> |
| 232 | + </Table> |
| 233 | + ); |
| 234 | +}; |
| 235 | + |
| 236 | +const TxtRecordValueCell = ({ value }: { value: string }): JSX.Element => { |
| 237 | + const { onCopy, hasCopied } = useClipboard(value); |
| 238 | + |
| 239 | + return ( |
| 240 | + <Flex |
| 241 | + align='center' |
| 242 | + sx={t => ({ gap: t.space.$2, minWidth: 0 })} |
| 243 | + > |
| 244 | + <Text |
| 245 | + as='span' |
| 246 | + colorScheme='secondary' |
| 247 | + title={value} |
| 248 | + sx={{ |
| 249 | + fontFamily: 'monospace', |
| 250 | + display: 'block', |
| 251 | + overflow: 'hidden', |
| 252 | + textOverflow: 'ellipsis', |
| 253 | + whiteSpace: 'nowrap', |
| 254 | + minWidth: 0, |
| 255 | + flex: 1, |
| 256 | + }} |
| 257 | + > |
| 258 | + {value} |
| 259 | + </Text> |
| 260 | + <Button |
| 261 | + colorScheme='secondary' |
| 262 | + aria-label='Copy value' |
| 263 | + onClick={() => onCopy()} |
| 264 | + sx={t => ({ flexShrink: 0, padding: t.space.$1 })} |
| 265 | + > |
| 266 | + <Icon |
| 267 | + icon={hasCopied ? Checkmark : Clipboard} |
| 268 | + sx={t => ({ width: t.sizes.$4, height: t.sizes.$4, color: t.colors.$colorMutedForeground })} |
| 269 | + /> |
| 270 | + </Button> |
| 271 | + </Flex> |
| 272 | + ); |
| 273 | +}; |
0 commit comments