implemented service page design
This commit is contained in:
parent
4fd02288cd
commit
72123aee2f
@ -9,13 +9,16 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": ">=7.0.0 <8.0.0",
|
||||
"@headlessui/react": "^1.6.3",
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"clsx": "^1.1.1",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"react-query": "^3.39.0",
|
||||
"react-router-dom": "6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^7.1.0",
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"@vitejs/plugin-react": "^1.3.0",
|
||||
|
168
frontend/pnpm-lock.yaml
generated
168
frontend/pnpm-lock.yaml
generated
@ -2,6 +2,8 @@ lockfileVersion: 5.4
|
||||
|
||||
specifiers:
|
||||
'@babel/core': '>=7.0.0 <8.0.0'
|
||||
'@faker-js/faker': ^7.1.0
|
||||
'@headlessui/react': ^1.6.3
|
||||
'@heroicons/react': ^1.0.6
|
||||
'@types/react': ^18.0.0
|
||||
'@types/react-dom': ^18.0.0
|
||||
@ -11,6 +13,7 @@ specifiers:
|
||||
postcss: ^8.4.14
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
react-query: ^3.39.0
|
||||
react-router-dom: '6'
|
||||
tailwindcss: ^3.0.24
|
||||
typescript: ^4.6.3
|
||||
@ -18,13 +21,16 @@ specifiers:
|
||||
|
||||
dependencies:
|
||||
'@babel/core': 7.17.10
|
||||
'@headlessui/react': 1.6.3_ef5jwxihqo6n7gxfmzogljlgcm
|
||||
'@heroicons/react': 1.0.6_react@18.1.0
|
||||
clsx: 1.1.1
|
||||
react: 18.1.0
|
||||
react-dom: 18.1.0_react@18.1.0
|
||||
react-query: 3.39.0_ef5jwxihqo6n7gxfmzogljlgcm
|
||||
react-router-dom: 6.3.0_ef5jwxihqo6n7gxfmzogljlgcm
|
||||
|
||||
devDependencies:
|
||||
'@faker-js/faker': 7.1.0
|
||||
'@types/react': 18.0.9
|
||||
'@types/react-dom': 18.0.4
|
||||
'@vitejs/plugin-react': 1.3.2
|
||||
@ -283,6 +289,22 @@ packages:
|
||||
'@babel/helper-validator-identifier': 7.16.7
|
||||
to-fast-properties: 2.0.0
|
||||
|
||||
/@faker-js/faker/7.1.0:
|
||||
resolution: {integrity: sha512-G+EvE29QUd9/6GTrwA/TK2AiN79W4ZG6kyJqj2RAqUqEd8A8ICnjA3Rj3F2IaaA+sq6WJzs4lh8GJJRDG1UH7A==}
|
||||
engines: {node: '>=14.0.0', npm: '>=6.0.0'}
|
||||
dev: true
|
||||
|
||||
/@headlessui/react/1.6.3_ef5jwxihqo6n7gxfmzogljlgcm:
|
||||
resolution: {integrity: sha512-WNu/ypGzl0JmJ+sD34KtdycEu2n7EZjKFx2rq6fivsszPdoEyOVZ/GYQMJ437dfAJI0/ZxoRYfrOVduZHjlokQ==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
react: ^16 || ^17 || ^18
|
||||
react-dom: ^16 || ^17 || ^18
|
||||
dependencies:
|
||||
react: 18.1.0
|
||||
react-dom: 18.1.0_react@18.1.0
|
||||
dev: false
|
||||
|
||||
/@heroicons/react/1.0.6_react@18.1.0:
|
||||
resolution: {integrity: sha512-JJCXydOFWMDpCP4q13iEplA503MQO3xLoZiKum+955ZCtHINWnx26CUxVxxFQu/uLb4LW3ge15ZpzIkXKkJ8oQ==}
|
||||
peerDependencies:
|
||||
@ -435,11 +457,27 @@ packages:
|
||||
postcss-value-parser: 4.2.0
|
||||
dev: true
|
||||
|
||||
/balanced-match/1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
dev: false
|
||||
|
||||
/big-integer/1.6.51:
|
||||
resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==}
|
||||
engines: {node: '>=0.6'}
|
||||
dev: false
|
||||
|
||||
/binary-extensions/2.2.0:
|
||||
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/brace-expansion/1.1.11:
|
||||
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
concat-map: 0.0.1
|
||||
dev: false
|
||||
|
||||
/braces/3.0.2:
|
||||
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
|
||||
engines: {node: '>=8'}
|
||||
@ -447,6 +485,19 @@ packages:
|
||||
fill-range: 7.0.1
|
||||
dev: true
|
||||
|
||||
/broadcast-channel/3.7.0:
|
||||
resolution: {integrity: sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.18.0
|
||||
detect-node: 2.1.0
|
||||
js-sha3: 0.8.0
|
||||
microseconds: 0.2.0
|
||||
nano-time: 1.0.0
|
||||
oblivious-set: 1.0.0
|
||||
rimraf: 3.0.2
|
||||
unload: 2.2.0
|
||||
dev: false
|
||||
|
||||
/browserslist/4.20.3:
|
||||
resolution: {integrity: sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==}
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
@ -506,6 +557,10 @@ packages:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
dev: true
|
||||
|
||||
/concat-map/0.0.1:
|
||||
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
|
||||
dev: false
|
||||
|
||||
/convert-source-map/1.8.0:
|
||||
resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==}
|
||||
dependencies:
|
||||
@ -536,6 +591,10 @@ packages:
|
||||
resolution: {integrity: sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=}
|
||||
dev: true
|
||||
|
||||
/detect-node/2.1.0:
|
||||
resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==}
|
||||
dev: false
|
||||
|
||||
/detective/5.2.0:
|
||||
resolution: {integrity: sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
@ -805,6 +864,10 @@ packages:
|
||||
resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
|
||||
dev: true
|
||||
|
||||
/fs.realpath/1.0.0:
|
||||
resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=}
|
||||
dev: false
|
||||
|
||||
/fsevents/2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
@ -835,6 +898,17 @@ packages:
|
||||
is-glob: 4.0.3
|
||||
dev: true
|
||||
|
||||
/glob/7.2.3:
|
||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||
dependencies:
|
||||
fs.realpath: 1.0.0
|
||||
inflight: 1.0.6
|
||||
inherits: 2.0.4
|
||||
minimatch: 3.1.2
|
||||
once: 1.4.0
|
||||
path-is-absolute: 1.0.1
|
||||
dev: false
|
||||
|
||||
/globals/11.12.0:
|
||||
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
|
||||
engines: {node: '>=4'}
|
||||
@ -856,6 +930,17 @@ packages:
|
||||
'@babel/runtime': 7.18.0
|
||||
dev: false
|
||||
|
||||
/inflight/1.0.6:
|
||||
resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=}
|
||||
dependencies:
|
||||
once: 1.4.0
|
||||
wrappy: 1.0.2
|
||||
dev: false
|
||||
|
||||
/inherits/2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
dev: false
|
||||
|
||||
/is-binary-path/2.1.0:
|
||||
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
|
||||
engines: {node: '>=8'}
|
||||
@ -886,6 +971,10 @@ packages:
|
||||
engines: {node: '>=0.12.0'}
|
||||
dev: true
|
||||
|
||||
/js-sha3/0.8.0:
|
||||
resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==}
|
||||
dev: false
|
||||
|
||||
/js-tokens/4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
@ -911,6 +1000,13 @@ packages:
|
||||
js-tokens: 4.0.0
|
||||
dev: false
|
||||
|
||||
/match-sorter/6.3.1:
|
||||
resolution: {integrity: sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.18.0
|
||||
remove-accents: 0.4.2
|
||||
dev: false
|
||||
|
||||
/merge2/1.4.1:
|
||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||
engines: {node: '>= 8'}
|
||||
@ -924,6 +1020,16 @@ packages:
|
||||
picomatch: 2.3.1
|
||||
dev: true
|
||||
|
||||
/microseconds/0.2.0:
|
||||
resolution: {integrity: sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==}
|
||||
dev: false
|
||||
|
||||
/minimatch/3.1.2:
|
||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
dependencies:
|
||||
brace-expansion: 1.1.11
|
||||
dev: false
|
||||
|
||||
/minimist/1.2.6:
|
||||
resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==}
|
||||
dev: true
|
||||
@ -931,6 +1037,12 @@ packages:
|
||||
/ms/2.1.2:
|
||||
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
||||
|
||||
/nano-time/1.0.0:
|
||||
resolution: {integrity: sha1-sFVPaa2J4i0JB/ehKwmTpdlhN+8=}
|
||||
dependencies:
|
||||
big-integer: 1.6.51
|
||||
dev: false
|
||||
|
||||
/nanoid/3.3.4:
|
||||
resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
@ -955,6 +1067,21 @@ packages:
|
||||
engines: {node: '>= 6'}
|
||||
dev: true
|
||||
|
||||
/oblivious-set/1.0.0:
|
||||
resolution: {integrity: sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==}
|
||||
dev: false
|
||||
|
||||
/once/1.4.0:
|
||||
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
dev: false
|
||||
|
||||
/path-is-absolute/1.0.1:
|
||||
resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/path-parse/1.0.7:
|
||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||
dev: true
|
||||
@ -1044,6 +1171,25 @@ packages:
|
||||
scheduler: 0.22.0
|
||||
dev: false
|
||||
|
||||
/react-query/3.39.0_ef5jwxihqo6n7gxfmzogljlgcm:
|
||||
resolution: {integrity: sha512-Od0IkSuS79WJOhzWBx/ys0x13+7wFqgnn64vBqqAAnZ9whocVhl/y1padD5uuZ6EIkXbFbInax0qvY7zGM0thA==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: '*'
|
||||
react-native: '*'
|
||||
peerDependenciesMeta:
|
||||
react-dom:
|
||||
optional: true
|
||||
react-native:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.18.0
|
||||
broadcast-channel: 3.7.0
|
||||
match-sorter: 6.3.1
|
||||
react: 18.1.0
|
||||
react-dom: 18.1.0_react@18.1.0
|
||||
dev: false
|
||||
|
||||
/react-refresh/0.13.0:
|
||||
resolution: {integrity: sha512-XP8A9BT0CpRBD+NYLLeIhld/RqG9+gktUjW1FkE+Vm7OCinbG1SshcK5tb9ls4kzvjZr9mOQc7HYgBngEyPAXg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -1088,6 +1234,10 @@ packages:
|
||||
resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==}
|
||||
dev: false
|
||||
|
||||
/remove-accents/0.4.2:
|
||||
resolution: {integrity: sha1-CkPTqq4egNuRngeuJUsoXZ4ce7U=}
|
||||
dev: false
|
||||
|
||||
/resolve/1.22.0:
|
||||
resolution: {integrity: sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==}
|
||||
hasBin: true
|
||||
@ -1102,6 +1252,13 @@ packages:
|
||||
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/rimraf/3.0.2:
|
||||
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
glob: 7.2.3
|
||||
dev: false
|
||||
|
||||
/rollup/2.72.1:
|
||||
resolution: {integrity: sha512-NTc5UGy/NWFGpSqF1lFY8z9Adri6uhyMLI6LvPAXdBKoPRFhIIiBUpt+Qg2awixqO3xvzSijjhnb4+QEZwJmxA==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
@ -1192,6 +1349,13 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/unload/2.2.0:
|
||||
resolution: {integrity: sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.18.0
|
||||
detect-node: 2.1.0
|
||||
dev: false
|
||||
|
||||
/util-deprecate/1.0.2:
|
||||
resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=}
|
||||
dev: true
|
||||
@ -1220,6 +1384,10 @@ packages:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
|
||||
/wrappy/1.0.2:
|
||||
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
|
||||
dev: false
|
||||
|
||||
/xtend/4.0.2:
|
||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||
engines: {node: '>=0.4'}
|
||||
|
@ -1,10 +1,33 @@
|
||||
import { FC, HTMLProps } from "react";
|
||||
import clsx from "clsx";
|
||||
import { FC, MouseEventHandler, ReactNode } from "react";
|
||||
import { InputSize } from "../../types";
|
||||
|
||||
const Button: FC<HTMLProps<HTMLButtonElement>> = (props) => {
|
||||
interface InputProps {
|
||||
onClick?: MouseEventHandler<HTMLButtonElement> | undefined;
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
size?: InputSize;
|
||||
}
|
||||
|
||||
const getButtonSize = (size?: InputSize) => {
|
||||
switch (size) {
|
||||
case "md":
|
||||
return "px-[1rem] py-[.5rem] text-sm";
|
||||
default:
|
||||
return "px-10 py-4 text-base";
|
||||
}
|
||||
};
|
||||
|
||||
const Button: FC<InputProps> = (props) => {
|
||||
const btnClassName = getButtonSize(props.size);
|
||||
return (
|
||||
<button
|
||||
onClick={props.onClick}
|
||||
className="flex items-center justify-center w-full px-10 py-4 text-base font-medium text-center text-white transition duration-500 ease-in-out transform bg-blue-600 rounded-xl hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
className={clsx(
|
||||
"flex items-center justify-center w-full font-medium text-center text-white transition duration-500 ease-in-out transform bg-blue-600 rounded-xl hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500",
|
||||
btnClassName,
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</button>
|
||||
|
11
frontend/src/components/atoms/InlineLink.tsx
Normal file
11
frontend/src/components/atoms/InlineLink.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import { FC, HTMLProps } from "react";
|
||||
|
||||
const InlineLink: FC<HTMLProps<HTMLAnchorElement>> = (props) => {
|
||||
return (
|
||||
<a className="text-sky-400 inline-block" href={props.href}>
|
||||
{props.children}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export default InlineLink;
|
@ -1,14 +1,44 @@
|
||||
import { FC, HTMLProps } from "react";
|
||||
import clsx from "clsx";
|
||||
import { FC } from "react";
|
||||
import { InputSize } from "../../types";
|
||||
|
||||
const Input: FC<HTMLProps<HTMLLabelElement>> = (props) => {
|
||||
interface InputProps {
|
||||
id?: string;
|
||||
type?: string;
|
||||
className?: string;
|
||||
placeholder?: string;
|
||||
required?: boolean;
|
||||
name?: string;
|
||||
size?: InputSize;
|
||||
value?: string;
|
||||
onInput?: React.FormEventHandler<HTMLInputElement>;
|
||||
}
|
||||
|
||||
const getInputSize = (size?: InputSize) => {
|
||||
switch (size) {
|
||||
case "md":
|
||||
return "px-[1rem] py-[.5rem] text-sm";
|
||||
default:
|
||||
return "px-5 py-3 text-base";
|
||||
}
|
||||
};
|
||||
|
||||
const Input: FC<InputProps> = (props) => {
|
||||
const sizeClassNames = getInputSize(props.size);
|
||||
return (
|
||||
<input
|
||||
id={props.id}
|
||||
name={props.name}
|
||||
type={props.type}
|
||||
value={props.value}
|
||||
onInput={props.onInput}
|
||||
placeholder={props.placeholder}
|
||||
required={props.required}
|
||||
className="block w-full px-5 py-3 text-base text-neutral-600 placeholder-gray-300 transition duration-500 ease-in-out transform border border-transparent rounded-lg bg-gray-50 focus:outline-none focus:border-transparent focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-300 shadow-sm"
|
||||
className={clsx(
|
||||
"block w-full text-neutral-600 placeholder-gray-300 transition duration-500 ease-in-out transform border border-transparent rounded-lg bg-gray-50 focus:outline-none focus:border-transparent focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-300 shadow-sm",
|
||||
sizeClassNames,
|
||||
props.className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -1,12 +1,17 @@
|
||||
import { FC, HTMLProps } from "react";
|
||||
|
||||
const InputLabel: FC<HTMLProps<HTMLLabelElement>> = (props) => {
|
||||
interface InputLabelProps extends HTMLProps<HTMLLabelElement> {
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
const InputLabel: FC<InputLabelProps> = (props) => {
|
||||
return (
|
||||
<label
|
||||
htmlFor={props.id}
|
||||
className="block text-sm font-medium text-gray-700"
|
||||
>
|
||||
{props.children}
|
||||
{props.required && <span className="text-red-600">*</span>}
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
7
frontend/src/components/layouts/Container.tsx
Normal file
7
frontend/src/components/layouts/Container.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import { FC, HTMLProps } from "react";
|
||||
|
||||
const Container: FC<HTMLProps<HTMLDivElement>> = (props) => {
|
||||
return <div className="w-full lg:w-1/2 mx-auto">{props.children}</div>;
|
||||
};
|
||||
|
||||
export default Container;
|
@ -7,6 +7,7 @@ import {
|
||||
} from "@heroicons/react/outline";
|
||||
import { FC, ReactNode } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import useServices from "../../hooks/useServices";
|
||||
import Badge from "../atoms/Badge";
|
||||
import InputLabel from "../atoms/InputLabel";
|
||||
|
||||
@ -70,7 +71,7 @@ const Sidebar = () => {
|
||||
<ul className="space-y-1">
|
||||
<li>
|
||||
<SidebarTab
|
||||
badge={<Badge>0</Badge>}
|
||||
badge={<Badge>12</Badge>}
|
||||
onClick={navFactory("/services")}
|
||||
>
|
||||
<PuzzleIcon className="w-6 h-6 text-gray-700" />
|
||||
|
@ -1,17 +1,34 @@
|
||||
import { FC, HTMLProps } from "react";
|
||||
import { FC } from "react";
|
||||
import { InputSize } from "../../types";
|
||||
import Input from "../atoms/Input";
|
||||
import InputLabel from "../atoms/InputLabel";
|
||||
|
||||
const TextField: FC<HTMLProps<HTMLInputElement>> = (props) => {
|
||||
interface TextFieldProps {
|
||||
id?: string;
|
||||
type?: string;
|
||||
className?: string;
|
||||
placeholder?: string;
|
||||
required?: boolean;
|
||||
name?: string;
|
||||
size?: InputSize;
|
||||
label?: string;
|
||||
inputClassName?: string;
|
||||
}
|
||||
|
||||
const TextField: FC<TextFieldProps> = (props) => {
|
||||
return (
|
||||
<div>
|
||||
<InputLabel id={props.id}>{props.label}</InputLabel>
|
||||
<div className="w-full">
|
||||
<InputLabel id={props.id} required={props.required}>
|
||||
{props.label}
|
||||
</InputLabel>
|
||||
<div className="mt-1">
|
||||
<Input
|
||||
id={props.id}
|
||||
type={props.type}
|
||||
className={props.inputClassName}
|
||||
placeholder={props.placeholder}
|
||||
required={props.required}
|
||||
size={props.size}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,9 +1,104 @@
|
||||
import { PlusIcon } from "@heroicons/react/outline";
|
||||
import clsx from "clsx";
|
||||
import { useState } from "react";
|
||||
import useServices from "../../../hooks/useServices";
|
||||
import Button from "../../atoms/Button";
|
||||
import InlineLink from "../../atoms/InlineLink";
|
||||
import Container from "../../layouts/Container";
|
||||
import DashboardLayout from "../../layouts/Dashboard";
|
||||
import TextField from "../../molecules/TextField";
|
||||
|
||||
const ServicesPage = () => {
|
||||
const [toggleCreate, setToggleCreate] = useState(false);
|
||||
const services = useServices();
|
||||
|
||||
const handleToggleCreate = () => {
|
||||
setToggleCreate(!toggleCreate);
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<div>Services</div>
|
||||
<Container>
|
||||
<h1 className="text-3xl font-bold mt-12 mb-8">Services</h1>
|
||||
<div className="mb-4 flex items-end justify-between">
|
||||
<TextField
|
||||
label="Search"
|
||||
size="md"
|
||||
placeholder="Search services"
|
||||
inputClassName="bg-white border border-gray-300 w-60 shadow-none"
|
||||
/>
|
||||
<Button
|
||||
size="md"
|
||||
className={clsx(
|
||||
"w-20 border border-sky-600",
|
||||
toggleCreate && "bg-white text-sky-600 hover:bg-white"
|
||||
)}
|
||||
onClick={handleToggleCreate}
|
||||
>
|
||||
<div className="flex flex-row space-x-1">
|
||||
<PlusIcon width={20} />
|
||||
<span>Add</span>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
{toggleCreate && (
|
||||
<div className="w-full bg-gray-50 border border-gray-200 rounded-lg p-5 mb-4">
|
||||
<h1 className="mb-4">Create a new service</h1>
|
||||
<div className="space-y-3">
|
||||
<TextField
|
||||
label="Name"
|
||||
required={true}
|
||||
placeholder="service name"
|
||||
size="md"
|
||||
inputClassName="shadow-none border border-gray-300 bg-white"
|
||||
/>
|
||||
<TextField
|
||||
label="Destination"
|
||||
required={true}
|
||||
placeholder="url"
|
||||
size="md"
|
||||
inputClassName="shadow-none border border-gray-300 bg-white"
|
||||
/>
|
||||
<div className="flex justify-end items-center">
|
||||
<Button size="md" className="w-20 border border-sky-600">
|
||||
Create
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="rounded-lg border">
|
||||
<table className="w-full border-collapse">
|
||||
<thead className="h-12">
|
||||
<tr className="text-left font-semibold text-sm border-b bg-gray-50">
|
||||
<th className="px-3 py-2 lg:w-2/12">Name</th>
|
||||
<th className="px-3 py-2 lg:w-4/12">Destination</th>
|
||||
<th className="px-3 py-2 lg:w-5/12">Referenced by</th>
|
||||
<th className="px-3 py-2 lg:w-1/12">{/* Delete */}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{services.data &&
|
||||
services.data.map((service) => (
|
||||
<tr key={service.name} className="h-12 border-b ">
|
||||
<td className="px-3 py-2 truncate">{service.name}</td>
|
||||
<td className="px-3 py-2 truncate">
|
||||
<InlineLink href="https://example.com/">
|
||||
{service.destination[0]}
|
||||
</InlineLink>
|
||||
</td>
|
||||
<td className="px-3 py-2 truncate">
|
||||
<InlineLink href="https://example.com/">
|
||||
{service.references}
|
||||
</InlineLink>
|
||||
</td>
|
||||
<td className="py-2 cursor-pointer text-sky-900">Delete</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Container>
|
||||
</DashboardLayout>
|
||||
);
|
||||
};
|
||||
|
19
frontend/src/hooks/useServices.tsx
Normal file
19
frontend/src/hooks/useServices.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { useQuery } from "react-query";
|
||||
import { Service } from "../types";
|
||||
|
||||
let services = Array(11)
|
||||
.fill(0)
|
||||
.map(() => ({
|
||||
name: faker.name.lastName(),
|
||||
references: [faker.name.firstName()],
|
||||
destination: [faker.internet.ipv4()],
|
||||
}));
|
||||
|
||||
const fetchServices = async (): Promise<Service[]> => {
|
||||
return services;
|
||||
};
|
||||
|
||||
export default function useServices() {
|
||||
return useQuery(["services"], fetchServices);
|
||||
}
|
@ -1,10 +1,19 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { QueryClient, QueryClientProvider } from "react-query";
|
||||
import { ReactQueryDevtools } from "react-query/devtools";
|
||||
import App from "./App";
|
||||
import "./index.css";
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<App />
|
||||
<ReactQueryDevtools initialIsOpen />
|
||||
</QueryClientProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
export { queryClient };
|
||||
|
7
frontend/src/types.ts
Normal file
7
frontend/src/types.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export type InputSize = "sm" | "md" | "lg";
|
||||
|
||||
export interface Service {
|
||||
name: string;
|
||||
references: string[];
|
||||
destination: string[];
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user