mirror of
				https://github.com/optim-enterprises-bv/OptimCloud-gw-ui.git
				synced 2025-10-31 02:07:45 +00:00 
			
		
		
		
	[WIFI-11543] Added API keys to profile page
Signed-off-by: Charles <charles.bourque96@gmail.com>
This commit is contained in:
		
							
								
								
									
										29
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										29
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,12 +1,12 @@ | |||||||
| { | { | ||||||
|   "name": "ucentral-client", |   "name": "ucentral-client", | ||||||
|   "version": "2.8.0(10)", |   "version": "2.8.0(11)", | ||||||
|   "lockfileVersion": 2, |   "lockfileVersion": 2, | ||||||
|   "requires": true, |   "requires": true, | ||||||
|   "packages": { |   "packages": { | ||||||
|     "": { |     "": { | ||||||
|       "name": "ucentral-client", |       "name": "ucentral-client", | ||||||
|       "version": "2.8.0(10)", |       "version": "2.8.0(11)", | ||||||
|       "license": "ISC", |       "license": "ISC", | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@chakra-ui/icons": "^2.0.11", |         "@chakra-ui/icons": "^2.0.11", | ||||||
| @@ -57,6 +57,7 @@ | |||||||
|         "@types/node": "^18.11.2", |         "@types/node": "^18.11.2", | ||||||
|         "@types/react": "^18.0.21", |         "@types/react": "^18.0.21", | ||||||
|         "@types/react-csv": "^1.1.3", |         "@types/react-csv": "^1.1.3", | ||||||
|  |         "@types/react-datepicker": "4.8.0", | ||||||
|         "@types/react-dom": "^18.0.6", |         "@types/react-dom": "^18.0.6", | ||||||
|         "@types/react-table": "^7.7.12", |         "@types/react-table": "^7.7.12", | ||||||
|         "@types/react-virtualized-auto-sizer": "^1.0.1", |         "@types/react-virtualized-auto-sizer": "^1.0.1", | ||||||
| @@ -3551,6 +3552,18 @@ | |||||||
|         "@types/react": "*" |         "@types/react": "*" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/@types/react-datepicker": { | ||||||
|  |       "version": "4.8.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-4.8.0.tgz", | ||||||
|  |       "integrity": "sha512-20uzZsIf4moPAjjHDfPvH8UaOHZBxrkiQZoLS3wgKq8Xhp+95gdercLEdoA7/I8nR9R5Jz2qQkdMIM+Lq4AS1A==", | ||||||
|  |       "dev": true, | ||||||
|  |       "dependencies": { | ||||||
|  |         "@popperjs/core": "^2.9.2", | ||||||
|  |         "@types/react": "*", | ||||||
|  |         "date-fns": "^2.0.1", | ||||||
|  |         "react-popper": "^2.2.5" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "node_modules/@types/react-dom": { |     "node_modules/@types/react-dom": { | ||||||
|       "version": "18.0.6", |       "version": "18.0.6", | ||||||
|       "dev": true, |       "dev": true, | ||||||
| @@ -11815,6 +11828,18 @@ | |||||||
|         "@types/react": "*" |         "@types/react": "*" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "@types/react-datepicker": { | ||||||
|  |       "version": "4.8.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-4.8.0.tgz", | ||||||
|  |       "integrity": "sha512-20uzZsIf4moPAjjHDfPvH8UaOHZBxrkiQZoLS3wgKq8Xhp+95gdercLEdoA7/I8nR9R5Jz2qQkdMIM+Lq4AS1A==", | ||||||
|  |       "dev": true, | ||||||
|  |       "requires": { | ||||||
|  |         "@popperjs/core": "^2.9.2", | ||||||
|  |         "@types/react": "*", | ||||||
|  |         "date-fns": "^2.0.1", | ||||||
|  |         "react-popper": "^2.2.5" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "@types/react-dom": { |     "@types/react-dom": { | ||||||
|       "version": "18.0.6", |       "version": "18.0.6", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "ucentral-client", |   "name": "ucentral-client", | ||||||
|   "version": "2.8.0(10)", |   "version": "2.8.0(11)", | ||||||
|   "description": "", |   "description": "", | ||||||
|   "private": true, |   "private": true, | ||||||
|   "main": "index.tsx", |   "main": "index.tsx", | ||||||
| @@ -65,6 +65,7 @@ | |||||||
|     "@types/react-csv": "^1.1.3", |     "@types/react-csv": "^1.1.3", | ||||||
|     "@types/react-dom": "^18.0.6", |     "@types/react-dom": "^18.0.6", | ||||||
|     "@types/react-table": "^7.7.12", |     "@types/react-table": "^7.7.12", | ||||||
|  |     "@types/react-datepicker": "4.8.0", | ||||||
|     "@types/uuid": "^8.3.4", |     "@types/uuid": "^8.3.4", | ||||||
|     "@types/react-virtualized-auto-sizer": "^1.0.1", |     "@types/react-virtualized-auto-sizer": "^1.0.1", | ||||||
|     "@types/react-window": "^1.8.5", |     "@types/react-window": "^1.8.5", | ||||||
|   | |||||||
| @@ -245,6 +245,7 @@ | |||||||
| 		"identification": "Identifizierung", | 		"identification": "Identifizierung", | ||||||
| 		"inherit": "Erben", | 		"inherit": "Erben", | ||||||
| 		"language": "Sprache", | 		"language": "Sprache", | ||||||
|  | 		"last_use": "Zuletzt verwendeten", | ||||||
| 		"lifetime": "Lebenszeit", | 		"lifetime": "Lebenszeit", | ||||||
| 		"locale": "Gebietsschema", | 		"locale": "Gebietsschema", | ||||||
| 		"logout": "Ausloggen", | 		"logout": "Ausloggen", | ||||||
| @@ -261,6 +262,7 @@ | |||||||
| 		"model": "Modell", | 		"model": "Modell", | ||||||
| 		"modified": "Geändert", | 		"modified": "Geändert", | ||||||
| 		"monthly": "Monatlich", | 		"monthly": "Monatlich", | ||||||
|  | 		"months": "Monate", | ||||||
| 		"my_account": "Mein Konto", | 		"my_account": "Mein Konto", | ||||||
| 		"name": "Name", | 		"name": "Name", | ||||||
| 		"name_error": "Der Name muss weniger als 50 Zeichen lang sein", | 		"name_error": "Der Name muss weniger als 50 Zeichen lang sein", | ||||||
| @@ -314,6 +316,7 @@ | |||||||
| 		"use_file": "Datei verwenden", | 		"use_file": "Datei verwenden", | ||||||
| 		"value": "Wert", | 		"value": "Wert", | ||||||
| 		"variable": "Variable", | 		"variable": "Variable", | ||||||
|  | 		"view": "Aussicht", | ||||||
| 		"view_details": "Details anzeigen", | 		"view_details": "Details anzeigen", | ||||||
| 		"view_in_gateway": "Im Controller anzeigen", | 		"view_in_gateway": "Im Controller anzeigen", | ||||||
| 		"view_json": "JSON anzeigen", | 		"view_json": "JSON anzeigen", | ||||||
| @@ -742,6 +745,15 @@ | |||||||
| 		"successful_macs": "Erfolgreiche MACs", | 		"successful_macs": "Erfolgreiche MACs", | ||||||
| 		"title": "Arbeitsplätze" | 		"title": "Arbeitsplätze" | ||||||
| 	}, | 	}, | ||||||
|  | 	"keys": { | ||||||
|  | 		"description_error": "Die Beschreibung muss weniger als 64 Zeichen lang sein", | ||||||
|  | 		"expire_error": "Der Ablauf darf nicht mehr als ein Jahr in der Zukunft liegen", | ||||||
|  | 		"expires": "Läuft ab", | ||||||
|  | 		"max_keys": "Max. Schlüssel erreicht (10)", | ||||||
|  | 		"name_error": "Der Name sollte eindeutig sein und aus 6 bis 20 alphanumerischen Zeichen bestehen", | ||||||
|  | 		"one": "API-Schlüssel", | ||||||
|  | 		"other": "API-Schlüssel" | ||||||
|  | 	}, | ||||||
| 	"locations": { | 	"locations": { | ||||||
| 		"address_line_one": "Adresszeile eins", | 		"address_line_one": "Adresszeile eins", | ||||||
| 		"address_line_two": "Adresszeile zwei", | 		"address_line_two": "Adresszeile zwei", | ||||||
| @@ -802,7 +814,10 @@ | |||||||
| 		"receiving_types": "Typen empfangen", | 		"receiving_types": "Typen empfangen", | ||||||
| 		"security": "Sicherheit", | 		"security": "Sicherheit", | ||||||
| 		"source": "Quelle", | 		"source": "Quelle", | ||||||
| 		"thread": "Faden" | 		"thread": "Faden", | ||||||
|  | 		"venue_config": "Aufbau", | ||||||
|  | 		"venue_reboot": "Starten Sie neu", | ||||||
|  | 		"venue_upgrade": "Aktualisierung" | ||||||
| 	}, | 	}, | ||||||
| 	"map": { | 	"map": { | ||||||
| 		"auto_align": "Automatisch ausrichten", | 		"auto_align": "Automatisch ausrichten", | ||||||
| @@ -838,6 +853,22 @@ | |||||||
| 		"my_organization": "Meine Organisation", | 		"my_organization": "Meine Organisation", | ||||||
| 		"title": "Organisation" | 		"title": "Organisation" | ||||||
| 	}, | 	}, | ||||||
|  | 	"overrides": { | ||||||
|  | 		"delete_source": "Alle Überschreibungen von {{source}}löschen", | ||||||
|  | 		"ignore_overrides": "Konfigurationsüberschreibungen ignorieren", | ||||||
|  | 		"name_error": "Der Parameter ist bereits von Ihrer Quelle definiert", | ||||||
|  | 		"one": "Konfigurationsüberschreibung", | ||||||
|  | 		"other": "Konfigurationsüberschreibungen", | ||||||
|  | 		"param_name": "Parameter", | ||||||
|  | 		"param_value": "Wert", | ||||||
|  | 		"parameter": "Parameter", | ||||||
|  | 		"reason": "Grund", | ||||||
|  | 		"reason_error": "Ihr Grund muss weniger als 64 Zeichen lang sein. lang", | ||||||
|  | 		"source": "Quelle", | ||||||
|  | 		"tx_power_error": "Die Sendeleistung muss zwischen 1 und 32 liegen", | ||||||
|  | 		"update_success": "Aktualisierte Konfigurationsüberschreibungen!", | ||||||
|  | 		"value": "Wert" | ||||||
|  | 	}, | ||||||
| 	"profile": { | 	"profile": { | ||||||
| 		"about_me": "Über mich", | 		"about_me": "Über mich", | ||||||
| 		"activate": "", | 		"activate": "", | ||||||
|   | |||||||
| @@ -245,6 +245,7 @@ | |||||||
| 		"identification": "Identification", | 		"identification": "Identification", | ||||||
| 		"inherit": "Inherit", | 		"inherit": "Inherit", | ||||||
| 		"language": "Language", | 		"language": "Language", | ||||||
|  | 		"last_use": "Last Use", | ||||||
| 		"lifetime": "Lifetime", | 		"lifetime": "Lifetime", | ||||||
| 		"locale": "Locale", | 		"locale": "Locale", | ||||||
| 		"logout": "Logout", | 		"logout": "Logout", | ||||||
| @@ -261,6 +262,7 @@ | |||||||
| 		"model": "Model", | 		"model": "Model", | ||||||
| 		"modified": "Modified", | 		"modified": "Modified", | ||||||
| 		"monthly": "Monthly", | 		"monthly": "Monthly", | ||||||
|  | 		"months": "Months", | ||||||
| 		"my_account": "My Account", | 		"my_account": "My Account", | ||||||
| 		"name": "Name", | 		"name": "Name", | ||||||
| 		"name_error": "Name must be less than 50 characters long", | 		"name_error": "Name must be less than 50 characters long", | ||||||
| @@ -314,6 +316,7 @@ | |||||||
| 		"use_file": "Use File", | 		"use_file": "Use File", | ||||||
| 		"value": "Value", | 		"value": "Value", | ||||||
| 		"variable": "Variable", | 		"variable": "Variable", | ||||||
|  | 		"view": "View", | ||||||
| 		"view_details": "View Details", | 		"view_details": "View Details", | ||||||
| 		"view_in_gateway": "View In Controller", | 		"view_in_gateway": "View In Controller", | ||||||
| 		"view_json": "View JSON", | 		"view_json": "View JSON", | ||||||
| @@ -742,6 +745,15 @@ | |||||||
| 		"successful_macs": "Successful MACs", | 		"successful_macs": "Successful MACs", | ||||||
| 		"title": "Jobs" | 		"title": "Jobs" | ||||||
| 	}, | 	}, | ||||||
|  | 	"keys": { | ||||||
|  | 		"description_error": "Description needs to be less than 64 characters long", | ||||||
|  | 		"expire_error": "The expiry cannot be more than one year in the future", | ||||||
|  | 		"expires": "Expires", | ||||||
|  | 		"max_keys": "Max keys reached (10)", | ||||||
|  | 		"name_error": "Name should be unique and be between 6 and 20 alphanumeric characters", | ||||||
|  | 		"one": "API Key", | ||||||
|  | 		"other": "API Keys" | ||||||
|  | 	}, | ||||||
| 	"locations": { | 	"locations": { | ||||||
| 		"address_line_one": "Address Line One", | 		"address_line_one": "Address Line One", | ||||||
| 		"address_line_two": "Address Line Two", | 		"address_line_two": "Address Line Two", | ||||||
| @@ -802,7 +814,10 @@ | |||||||
| 		"receiving_types": "Receiving Types", | 		"receiving_types": "Receiving Types", | ||||||
| 		"security": "Security", | 		"security": "Security", | ||||||
| 		"source": "Source", | 		"source": "Source", | ||||||
| 		"thread": "Thread" | 		"thread": "Thread", | ||||||
|  | 		"venue_config": "Configuration", | ||||||
|  | 		"venue_reboot": "Reboot", | ||||||
|  | 		"venue_upgrade": "Upgrade" | ||||||
| 	}, | 	}, | ||||||
| 	"map": { | 	"map": { | ||||||
| 		"auto_align": "Auto Align", | 		"auto_align": "Auto Align", | ||||||
| @@ -838,6 +853,22 @@ | |||||||
| 		"my_organization": "My Organization", | 		"my_organization": "My Organization", | ||||||
| 		"title": "Organization" | 		"title": "Organization" | ||||||
| 	}, | 	}, | ||||||
|  | 	"overrides": { | ||||||
|  | 		"delete_source": "Delete all overrides from {{source}}", | ||||||
|  | 		"ignore_overrides": "Ignore Configuration Overrides", | ||||||
|  | 		"name_error": "Parameter is already defined by your source", | ||||||
|  | 		"one": "Configuration Override", | ||||||
|  | 		"other": "Configuration Overrides", | ||||||
|  | 		"param_name": "Parameter", | ||||||
|  | 		"param_value": "Value", | ||||||
|  | 		"parameter": "Parameter", | ||||||
|  | 		"reason": "Reason", | ||||||
|  | 		"reason_error": "Your reason needs to be less than 64 chars. long", | ||||||
|  | 		"source": "Source", | ||||||
|  | 		"tx_power_error": "Tx power needs to be between 1 and 32", | ||||||
|  | 		"update_success": "Updated Configuration Overrides!", | ||||||
|  | 		"value": "Value" | ||||||
|  | 	}, | ||||||
| 	"profile": { | 	"profile": { | ||||||
| 		"about_me": "About Me", | 		"about_me": "About Me", | ||||||
| 		"activate": "Activate", | 		"activate": "Activate", | ||||||
|   | |||||||
| @@ -245,6 +245,7 @@ | |||||||
| 		"identification": "identificación", | 		"identification": "identificación", | ||||||
| 		"inherit": "Heredar", | 		"inherit": "Heredar", | ||||||
| 		"language": "idioma", | 		"language": "idioma", | ||||||
|  | 		"last_use": "Utilizado por última vez", | ||||||
| 		"lifetime": "Toda la vida", | 		"lifetime": "Toda la vida", | ||||||
| 		"locale": "lugar", | 		"locale": "lugar", | ||||||
| 		"logout": "Cerrar sesión", | 		"logout": "Cerrar sesión", | ||||||
| @@ -261,6 +262,7 @@ | |||||||
| 		"model": "Modelo", | 		"model": "Modelo", | ||||||
| 		"modified": "Modificado", | 		"modified": "Modificado", | ||||||
| 		"monthly": "Mensual", | 		"monthly": "Mensual", | ||||||
|  | 		"months": "Meses", | ||||||
| 		"my_account": "Mi cuenta", | 		"my_account": "Mi cuenta", | ||||||
| 		"name": "Nombre", | 		"name": "Nombre", | ||||||
| 		"name_error": "El nombre debe tener menos de 50 caracteres", | 		"name_error": "El nombre debe tener menos de 50 caracteres", | ||||||
| @@ -314,6 +316,7 @@ | |||||||
| 		"use_file": "Usar archivo", | 		"use_file": "Usar archivo", | ||||||
| 		"value": "Valor", | 		"value": "Valor", | ||||||
| 		"variable": "Variable", | 		"variable": "Variable", | ||||||
|  | 		"view": "Ver", | ||||||
| 		"view_details": "Ver detalles", | 		"view_details": "Ver detalles", | ||||||
| 		"view_in_gateway": "Ver en controlador", | 		"view_in_gateway": "Ver en controlador", | ||||||
| 		"view_json": "Ver JSON", | 		"view_json": "Ver JSON", | ||||||
| @@ -742,6 +745,15 @@ | |||||||
| 		"successful_macs": "MAC exitosos", | 		"successful_macs": "MAC exitosos", | ||||||
| 		"title": "Trabajos" | 		"title": "Trabajos" | ||||||
| 	}, | 	}, | ||||||
|  | 	"keys": { | ||||||
|  | 		"description_error": "La descripción debe tener menos de 64 caracteres", | ||||||
|  | 		"expire_error": "El vencimiento no puede ser más de un año en el futuro", | ||||||
|  | 		"expires": "Vence", | ||||||
|  | 		"max_keys": "Número máximo de claves alcanzado (10)", | ||||||
|  | 		"name_error": "El nombre debe ser único y tener entre 6 y 20 caracteres alfanuméricos", | ||||||
|  | 		"one": "Clave API", | ||||||
|  | 		"other": "Claves de api" | ||||||
|  | 	}, | ||||||
| 	"locations": { | 	"locations": { | ||||||
| 		"address_line_one": "Dirección Línea Uno", | 		"address_line_one": "Dirección Línea Uno", | ||||||
| 		"address_line_two": "Dirección línea dos", | 		"address_line_two": "Dirección línea dos", | ||||||
| @@ -802,7 +814,10 @@ | |||||||
| 		"receiving_types": "Tipos de recepción", | 		"receiving_types": "Tipos de recepción", | ||||||
| 		"security": "SEGURIDAD", | 		"security": "SEGURIDAD", | ||||||
| 		"source": "Fuente", | 		"source": "Fuente", | ||||||
| 		"thread": "Hilo" | 		"thread": "Hilo", | ||||||
|  | 		"venue_config": "Configuración", | ||||||
|  | 		"venue_reboot": "Reiniciar", | ||||||
|  | 		"venue_upgrade": "Mejorar" | ||||||
| 	}, | 	}, | ||||||
| 	"map": { | 	"map": { | ||||||
| 		"auto_align": "Alineación automática", | 		"auto_align": "Alineación automática", | ||||||
| @@ -838,6 +853,22 @@ | |||||||
| 		"my_organization": "MI ORGANIZACION", | 		"my_organization": "MI ORGANIZACION", | ||||||
| 		"title": "Organización" | 		"title": "Organización" | ||||||
| 	}, | 	}, | ||||||
|  | 	"overrides": { | ||||||
|  | 		"delete_source": "Eliminar todas las anulaciones de {{source}}", | ||||||
|  | 		"ignore_overrides": "Ignorar anulaciones de configuración", | ||||||
|  | 		"name_error": "El parámetro ya está definido por su fuente", | ||||||
|  | 		"one": "Anulación de configuración", | ||||||
|  | 		"other": "Anulaciones de configuración", | ||||||
|  | 		"param_name": "parámetro", | ||||||
|  | 		"param_value": "Valor", | ||||||
|  | 		"parameter": "parámetro", | ||||||
|  | 		"reason": "Razón", | ||||||
|  | 		"reason_error": "Su motivo debe tener menos de 64 caracteres. largo", | ||||||
|  | 		"source": "Fuente", | ||||||
|  | 		"tx_power_error": "La potencia Tx debe estar entre 1 y 32", | ||||||
|  | 		"update_success": "¡Anulaciones de configuración actualizadas!", | ||||||
|  | 		"value": "Valor" | ||||||
|  | 	}, | ||||||
| 	"profile": { | 	"profile": { | ||||||
| 		"about_me": "Sobre mí", | 		"about_me": "Sobre mí", | ||||||
| 		"activate": "", | 		"activate": "", | ||||||
|   | |||||||
| @@ -245,6 +245,7 @@ | |||||||
| 		"identification": "Identification", | 		"identification": "Identification", | ||||||
| 		"inherit": "Hériter", | 		"inherit": "Hériter", | ||||||
| 		"language": "La langue", | 		"language": "La langue", | ||||||
|  | 		"last_use": "Dernière utilisation", | ||||||
| 		"lifetime": "durée de vie", | 		"lifetime": "durée de vie", | ||||||
| 		"locale": "lieu", | 		"locale": "lieu", | ||||||
| 		"logout": "Connectez - Out", | 		"logout": "Connectez - Out", | ||||||
| @@ -261,6 +262,7 @@ | |||||||
| 		"model": "Modèle", | 		"model": "Modèle", | ||||||
| 		"modified": "Modifié", | 		"modified": "Modifié", | ||||||
| 		"monthly": "Mensuel", | 		"monthly": "Mensuel", | ||||||
|  | 		"months": "mois", | ||||||
| 		"my_account": "Mon compte", | 		"my_account": "Mon compte", | ||||||
| 		"name": "Prénom", | 		"name": "Prénom", | ||||||
| 		"name_error": "Le nom doit comporter moins de 50 caractères", | 		"name_error": "Le nom doit comporter moins de 50 caractères", | ||||||
| @@ -314,6 +316,7 @@ | |||||||
| 		"use_file": "Utiliser le fichier", | 		"use_file": "Utiliser le fichier", | ||||||
| 		"value": "Valeur", | 		"value": "Valeur", | ||||||
| 		"variable": "Variable", | 		"variable": "Variable", | ||||||
|  | 		"view": "Vue", | ||||||
| 		"view_details": "Voir les détails", | 		"view_details": "Voir les détails", | ||||||
| 		"view_in_gateway": "Afficher dans le contrôleur", | 		"view_in_gateway": "Afficher dans le contrôleur", | ||||||
| 		"view_json": "Afficher JSON", | 		"view_json": "Afficher JSON", | ||||||
| @@ -742,6 +745,15 @@ | |||||||
| 		"successful_macs": "MAC réussis", | 		"successful_macs": "MAC réussis", | ||||||
| 		"title": "Emplois" | 		"title": "Emplois" | ||||||
| 	}, | 	}, | ||||||
|  | 	"keys": { | ||||||
|  | 		"description_error": "La description doit comporter moins de 64 caractères", | ||||||
|  | 		"expire_error": "L'expiration ne peut pas être supérieure à un an dans le futur", | ||||||
|  | 		"expires": "EXPIRÉ", | ||||||
|  | 		"max_keys": "Max de clés atteint (10)", | ||||||
|  | 		"name_error": "Le nom doit être unique et comporter entre 6 et 20 caractères alphanumériques", | ||||||
|  | 		"one": "Clé API", | ||||||
|  | 		"other": "Clés API" | ||||||
|  | 	}, | ||||||
| 	"locations": { | 	"locations": { | ||||||
| 		"address_line_one": "Adresse Ligne 1", | 		"address_line_one": "Adresse Ligne 1", | ||||||
| 		"address_line_two": "Adresse ligne deux", | 		"address_line_two": "Adresse ligne deux", | ||||||
| @@ -802,7 +814,10 @@ | |||||||
| 		"receiving_types": "Types de réception", | 		"receiving_types": "Types de réception", | ||||||
| 		"security": "SÉCURITÉ", | 		"security": "SÉCURITÉ", | ||||||
| 		"source": "La source", | 		"source": "La source", | ||||||
| 		"thread": "Fil de discussion" | 		"thread": "Fil de discussion", | ||||||
|  | 		"venue_config": "Configuration", | ||||||
|  | 		"venue_reboot": "Redémarrer", | ||||||
|  | 		"venue_upgrade": "Améliorer" | ||||||
| 	}, | 	}, | ||||||
| 	"map": { | 	"map": { | ||||||
| 		"auto_align": "Alignement automatique", | 		"auto_align": "Alignement automatique", | ||||||
| @@ -838,6 +853,22 @@ | |||||||
| 		"my_organization": "Mon organisation", | 		"my_organization": "Mon organisation", | ||||||
| 		"title": "Organisation" | 		"title": "Organisation" | ||||||
| 	}, | 	}, | ||||||
|  | 	"overrides": { | ||||||
|  | 		"delete_source": "Supprimer tous les remplacements de {{source}}", | ||||||
|  | 		"ignore_overrides": "Ignorer les remplacements de configuration", | ||||||
|  | 		"name_error": "Le paramètre est déjà défini par votre source", | ||||||
|  | 		"one": "Remplacement de la configuration", | ||||||
|  | 		"other": "Remplacements de configuration", | ||||||
|  | 		"param_name": "paramètre", | ||||||
|  | 		"param_value": "Valeur", | ||||||
|  | 		"parameter": "paramètre", | ||||||
|  | 		"reason": "raison", | ||||||
|  | 		"reason_error": "Votre raison doit être inférieure à 64 caractères. long", | ||||||
|  | 		"source": "La source", | ||||||
|  | 		"tx_power_error": "La puissance de transmission doit être comprise entre 1 et 32", | ||||||
|  | 		"update_success": "Remplacements de configuration mis à jour !", | ||||||
|  | 		"value": "Valeur" | ||||||
|  | 	}, | ||||||
| 	"profile": { | 	"profile": { | ||||||
| 		"about_me": "À propos de moi", | 		"about_me": "À propos de moi", | ||||||
| 		"activate": "", | 		"activate": "", | ||||||
|   | |||||||
| @@ -245,6 +245,7 @@ | |||||||
| 		"identification": "Identificação", | 		"identification": "Identificação", | ||||||
| 		"inherit": "Herdar", | 		"inherit": "Herdar", | ||||||
| 		"language": "Língua", | 		"language": "Língua", | ||||||
|  | 		"last_use": "Usado por último", | ||||||
| 		"lifetime": "Tempo de vida", | 		"lifetime": "Tempo de vida", | ||||||
| 		"locale": "Localidade", | 		"locale": "Localidade", | ||||||
| 		"logout": "Sair", | 		"logout": "Sair", | ||||||
| @@ -261,6 +262,7 @@ | |||||||
| 		"model": "Modelo", | 		"model": "Modelo", | ||||||
| 		"modified": "Modificado", | 		"modified": "Modificado", | ||||||
| 		"monthly": "Por mês", | 		"monthly": "Por mês", | ||||||
|  | 		"months": "Meses", | ||||||
| 		"my_account": "Minha conta", | 		"my_account": "Minha conta", | ||||||
| 		"name": "Nome", | 		"name": "Nome", | ||||||
| 		"name_error": "O nome deve ter menos de 50 caracteres", | 		"name_error": "O nome deve ter menos de 50 caracteres", | ||||||
| @@ -314,6 +316,7 @@ | |||||||
| 		"use_file": "Usar arquivo", | 		"use_file": "Usar arquivo", | ||||||
| 		"value": "Valor", | 		"value": "Valor", | ||||||
| 		"variable": "Variável", | 		"variable": "Variável", | ||||||
|  | 		"view": "Visão", | ||||||
| 		"view_details": "VER DETALHES", | 		"view_details": "VER DETALHES", | ||||||
| 		"view_in_gateway": "Ver no controlador", | 		"view_in_gateway": "Ver no controlador", | ||||||
| 		"view_json": "Ver JSON", | 		"view_json": "Ver JSON", | ||||||
| @@ -742,6 +745,15 @@ | |||||||
| 		"successful_macs": "MACs de sucesso", | 		"successful_macs": "MACs de sucesso", | ||||||
| 		"title": "Empregos" | 		"title": "Empregos" | ||||||
| 	}, | 	}, | ||||||
|  | 	"keys": { | ||||||
|  | 		"description_error": "A descrição precisa ter menos de 64 caracteres", | ||||||
|  | 		"expire_error": "A expiração não pode ser superior a um ano no futuro", | ||||||
|  | 		"expires": "expira", | ||||||
|  | 		"max_keys": "Teclas máximas alcançadas (10)", | ||||||
|  | 		"name_error": "O nome deve ser único e ter entre 6 e 20 caracteres alfanuméricos", | ||||||
|  | 		"one": "Chave API", | ||||||
|  | 		"other": "Chaves de Api" | ||||||
|  | 	}, | ||||||
| 	"locations": { | 	"locations": { | ||||||
| 		"address_line_one": "Linha de endereço um", | 		"address_line_one": "Linha de endereço um", | ||||||
| 		"address_line_two": "Linha de endereço dois", | 		"address_line_two": "Linha de endereço dois", | ||||||
| @@ -802,7 +814,10 @@ | |||||||
| 		"receiving_types": "Tipos de recebimento", | 		"receiving_types": "Tipos de recebimento", | ||||||
| 		"security": "SEGURANÇA", | 		"security": "SEGURANÇA", | ||||||
| 		"source": "Fonte", | 		"source": "Fonte", | ||||||
| 		"thread": "FIO" | 		"thread": "FIO", | ||||||
|  | 		"venue_config": "Configuração", | ||||||
|  | 		"venue_reboot": "Reiniciar", | ||||||
|  | 		"venue_upgrade": "Melhorar" | ||||||
| 	}, | 	}, | ||||||
| 	"map": { | 	"map": { | ||||||
| 		"auto_align": "Alinhamento Automático", | 		"auto_align": "Alinhamento Automático", | ||||||
| @@ -838,6 +853,22 @@ | |||||||
| 		"my_organization": "Minha organização", | 		"my_organization": "Minha organização", | ||||||
| 		"title": "Organização" | 		"title": "Organização" | ||||||
| 	}, | 	}, | ||||||
|  | 	"overrides": { | ||||||
|  | 		"delete_source": "Excluir todas as substituições de {{source}}", | ||||||
|  | 		"ignore_overrides": "Ignorar substituições de configuração", | ||||||
|  | 		"name_error": "O parâmetro já está definido pela sua fonte", | ||||||
|  | 		"one": "Substituição de configuração", | ||||||
|  | 		"other": "Substituições de configuração", | ||||||
|  | 		"param_name": "parâmetro", | ||||||
|  | 		"param_value": "Valor", | ||||||
|  | 		"parameter": "parâmetro", | ||||||
|  | 		"reason": "RAZÃO", | ||||||
|  | 		"reason_error": "Seu motivo precisa ter menos de 64 caracteres. grandes", | ||||||
|  | 		"source": "Fonte", | ||||||
|  | 		"tx_power_error": "A potência Tx precisa estar entre 1 e 32", | ||||||
|  | 		"update_success": "Substituições de configuração atualizadas!", | ||||||
|  | 		"value": "Valor" | ||||||
|  | 	}, | ||||||
| 	"profile": { | 	"profile": { | ||||||
| 		"about_me": "Sobre mim", | 		"about_me": "Sobre mim", | ||||||
| 		"activate": "", | 		"activate": "", | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								src/components/DatePickers/DatePickerInput/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/components/DatePickers/DatePickerInput/index.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | import React, { forwardRef } from 'react'; | ||||||
|  | import { Button } from '@chakra-ui/react'; | ||||||
|  |  | ||||||
|  | type Props = { | ||||||
|  |   value?: string; | ||||||
|  |   onClick?: () => void; | ||||||
|  |   isDisabled?: boolean; | ||||||
|  | }; | ||||||
|  | const DatePickerInput = forwardRef(({ value, onClick, isDisabled }: Props, ref: React.Ref<HTMLButtonElement>) => ( | ||||||
|  |   <Button colorScheme="gray" onClick={onClick} ref={ref} isDisabled={isDisabled}> | ||||||
|  |     {value} | ||||||
|  |   </Button> | ||||||
|  | )); | ||||||
|  |  | ||||||
|  | export default DatePickerInput; | ||||||
							
								
								
									
										37
									
								
								src/components/DatePickers/DateTimePicker/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/components/DatePickers/DateTimePicker/index.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | import React from 'react'; | ||||||
|  | import DatePicker from 'react-datepicker'; | ||||||
|  | import { useTranslation } from 'react-i18next'; | ||||||
|  | import 'react-datepicker/dist/react-datepicker.css'; | ||||||
|  | import DatePickerInput from '../DatePickerInput'; | ||||||
|  |  | ||||||
|  | type Props = { | ||||||
|  |   date: Date; | ||||||
|  |   onChange: (v: Date | null) => void; | ||||||
|  |   isStart?: boolean; | ||||||
|  |   isEnd?: boolean; | ||||||
|  |   startDate?: Date; | ||||||
|  |   endDate?: Date; | ||||||
|  |   isDisabled?: boolean; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const DateTimePicker = ({ date, onChange, isStart, isEnd, startDate, endDate, isDisabled }: Props) => { | ||||||
|  |   const { t } = useTranslation(); | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <DatePicker | ||||||
|  |       selected={date} | ||||||
|  |       onChange={onChange} | ||||||
|  |       selectsStart={isStart} | ||||||
|  |       selectsEnd={isEnd} | ||||||
|  |       startDate={startDate} | ||||||
|  |       endDate={endDate} | ||||||
|  |       timeInputLabel={`${t('common.time')}: `} | ||||||
|  |       dateFormat="dd/MM/yyyy hh:mm aa" | ||||||
|  |       timeFormat="p" | ||||||
|  |       showTimeSelect | ||||||
|  |       customInput={<DatePickerInput isDisabled={isDisabled} />} | ||||||
|  |     /> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default React.memo(DateTimePicker); | ||||||
							
								
								
									
										84
									
								
								src/hooks/Network/ApiKeys.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/hooks/Network/ApiKeys.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | |||||||
|  | import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; | ||||||
|  | import { axiosSec } from 'constants/axiosInstances'; | ||||||
|  | import { AxiosError } from 'models/Axios'; | ||||||
|  |  | ||||||
|  | export type ApiKey = { | ||||||
|  |   id: string; | ||||||
|  |   userUuid: string; | ||||||
|  |   name: string; | ||||||
|  |   description: string; | ||||||
|  |   apiKey: string; | ||||||
|  |   expiresOn: number; | ||||||
|  |   lastUse: number; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export type ApiKeyResponse = { | ||||||
|  |   apiKeys: ApiKey[]; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const getKeys = async (uuid?: string) => | ||||||
|  |   axiosSec | ||||||
|  |     .get(`apiKey/${uuid}`) | ||||||
|  |     .then(({ data }: { data: ApiKeyResponse }) => data) | ||||||
|  |     .catch((e: AxiosError) => { | ||||||
|  |       if (e.response?.status === 404) { | ||||||
|  |         return { | ||||||
|  |           apiKeys: [], | ||||||
|  |         } as ApiKeyResponse; | ||||||
|  |       } | ||||||
|  |       throw e; | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  | export const useGetUserApiKeys = ({ userId }: { userId?: string }) => | ||||||
|  |   useQuery(['apiKeys', userId], () => getKeys(userId), { | ||||||
|  |     enabled: userId !== undefined && userId !== '', | ||||||
|  |     staleTime: 1000 * 60 * 30, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  | const deleteKey = async ({ userId, keyId }: { userId: string; keyId: string }) => | ||||||
|  |   axiosSec.delete(`apiKey/${userId}?keyUuid=${keyId}`, {}); | ||||||
|  | export const useDeleteApiKey = ({ userId }: { userId?: string }) => { | ||||||
|  |   const queryClient = useQueryClient(); | ||||||
|  |  | ||||||
|  |   return useMutation(deleteKey, { | ||||||
|  |     onSuccess: () => { | ||||||
|  |       if (userId !== undefined && userId.length > 0) { | ||||||
|  |         queryClient.invalidateQueries(['apiKeys', userId]); | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const updateKey = async ({ description, userId, keyId }: { description: string; userId: string; keyId: string }) => | ||||||
|  |   axiosSec.put(`apiKey/${userId}?keyUuid=${keyId}`, { id: keyId, userUuid: userId, description }); | ||||||
|  | export const useUpdateApiKey = ({ userId }: { userId?: string }) => { | ||||||
|  |   const queryClient = useQueryClient(); | ||||||
|  |  | ||||||
|  |   return useMutation(updateKey, { | ||||||
|  |     onSuccess: () => { | ||||||
|  |       if (userId !== undefined && userId.length > 0) { | ||||||
|  |         queryClient.invalidateQueries(['apiKeys', userId]); | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const createKey = async ({ | ||||||
|  |   data, | ||||||
|  |   userId, | ||||||
|  | }: { | ||||||
|  |   userId: string; | ||||||
|  |   data: { userUuid: string; name: string; description: string; expiresOn: number }; | ||||||
|  | }) => axiosSec.post(`apiKey/${userId}`, data); | ||||||
|  |  | ||||||
|  | export const useCreateApiKey = ({ userId }: { userId?: string }) => { | ||||||
|  |   const queryClient = useQueryClient(); | ||||||
|  |  | ||||||
|  |   return useMutation(createKey, { | ||||||
|  |     onSuccess: () => { | ||||||
|  |       if (userId !== undefined && userId.length > 0) { | ||||||
|  |         queryClient.invalidateQueries(['apiKeys', userId]); | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | }; | ||||||
							
								
								
									
										140
									
								
								src/pages/Profile/ApiKeys/Table/Actions.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								src/pages/Profile/ApiKeys/Table/Actions.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | |||||||
|  | import React from 'react'; | ||||||
|  | import { CopyIcon } from '@chakra-ui/icons'; | ||||||
|  | import { | ||||||
|  |   IconButton, | ||||||
|  |   Tooltip, | ||||||
|  |   Popover, | ||||||
|  |   PopoverArrow, | ||||||
|  |   PopoverBody, | ||||||
|  |   PopoverCloseButton, | ||||||
|  |   PopoverContent, | ||||||
|  |   PopoverFooter, | ||||||
|  |   PopoverHeader, | ||||||
|  |   PopoverTrigger, | ||||||
|  |   Center, | ||||||
|  |   Box, | ||||||
|  |   Button, | ||||||
|  |   useDisclosure, | ||||||
|  |   HStack, | ||||||
|  |   Text, | ||||||
|  |   useClipboard, | ||||||
|  | } from '@chakra-ui/react'; | ||||||
|  | import { Eye, Trash } from 'phosphor-react'; | ||||||
|  | import { useTranslation } from 'react-i18next'; | ||||||
|  | import { ApiKey, useDeleteApiKey } from 'hooks/Network/ApiKeys'; | ||||||
|  |  | ||||||
|  | interface Props { | ||||||
|  |   apiKey: ApiKey; | ||||||
|  |   isDisabled?: boolean; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const ApiKeyActions = ({ apiKey, isDisabled }: Props) => { | ||||||
|  |   const { t } = useTranslation(); | ||||||
|  |   const { isOpen, onOpen, onClose } = useDisclosure(); | ||||||
|  |   const deleteKey = useDeleteApiKey({ userId: apiKey.userUuid }); | ||||||
|  |   const { hasCopied, onCopy } = useClipboard(apiKey.apiKey); | ||||||
|  |  | ||||||
|  |   const handleDeleteClick = React.useCallback(() => { | ||||||
|  |     deleteKey.mutate( | ||||||
|  |       { userId: apiKey.userUuid, keyId: apiKey.id }, | ||||||
|  |       { | ||||||
|  |         onSuccess: () => { | ||||||
|  |           onClose(); | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     ); | ||||||
|  |   }, []); | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <HStack mx="auto"> | ||||||
|  |       <Popover isOpen={isOpen} onOpen={onOpen} onClose={onClose}> | ||||||
|  |         <Tooltip hasArrow label={t('crud.delete')} placement="top" isDisabled={isOpen}> | ||||||
|  |           <Box> | ||||||
|  |             <PopoverTrigger> | ||||||
|  |               <IconButton | ||||||
|  |                 aria-label="delete-device" | ||||||
|  |                 colorScheme="red" | ||||||
|  |                 icon={<Trash size={20} />} | ||||||
|  |                 size="sm" | ||||||
|  |                 isDisabled={isDisabled} | ||||||
|  |               /> | ||||||
|  |             </PopoverTrigger> | ||||||
|  |           </Box> | ||||||
|  |         </Tooltip> | ||||||
|  |         <PopoverContent w="340px"> | ||||||
|  |           <PopoverArrow /> | ||||||
|  |           <PopoverCloseButton /> | ||||||
|  |           <PopoverHeader> | ||||||
|  |             {t('crud.delete')} {apiKey.name} | ||||||
|  |           </PopoverHeader> | ||||||
|  |           <PopoverBody> | ||||||
|  |             <Text whiteSpace="break-spaces">{t('crud.delete_confirm', { obj: t('keys.one') })}</Text> | ||||||
|  |           </PopoverBody> | ||||||
|  |           <PopoverFooter> | ||||||
|  |             <Center> | ||||||
|  |               <Button colorScheme="gray" mr="1" onClick={onClose}> | ||||||
|  |                 {t('common.cancel')} | ||||||
|  |               </Button> | ||||||
|  |               <Button colorScheme="red" ml="1" onClick={handleDeleteClick} isLoading={deleteKey.isLoading}> | ||||||
|  |                 {t('common.yes')} | ||||||
|  |               </Button> | ||||||
|  |             </Center> | ||||||
|  |           </PopoverFooter> | ||||||
|  |         </PopoverContent> | ||||||
|  |       </Popover> | ||||||
|  |       <Tooltip | ||||||
|  |         label={hasCopied ? `${t('common.copied')}!` : `${t('common.copy')} ${t('keys.one')}`} | ||||||
|  |         hasArrow | ||||||
|  |         closeOnClick={false} | ||||||
|  |       > | ||||||
|  |         <IconButton | ||||||
|  |           aria-label={t('common.copy')} | ||||||
|  |           icon={<CopyIcon h={5} w={5} />} | ||||||
|  |           onClick={onCopy} | ||||||
|  |           size="sm" | ||||||
|  |           colorScheme="teal" | ||||||
|  |           mr={2} | ||||||
|  |         /> | ||||||
|  |       </Tooltip> | ||||||
|  |       <Popover> | ||||||
|  |         <Tooltip label={`${t('common.view')} ${t('keys.one')}`} hasArrow closeOnClick={false}> | ||||||
|  |           <Box> | ||||||
|  |             <PopoverTrigger> | ||||||
|  |               <IconButton aria-label={t('common.view')} icon={<Eye size={20} />} size="sm" colorScheme="purple" /> | ||||||
|  |             </PopoverTrigger> | ||||||
|  |           </Box> | ||||||
|  |         </Tooltip> | ||||||
|  |         <PopoverContent w="560px"> | ||||||
|  |           <PopoverArrow /> | ||||||
|  |           <PopoverCloseButton /> | ||||||
|  |           <PopoverHeader> | ||||||
|  |             {t('common.view')} {apiKey.name} {t('keys.one')} | ||||||
|  |             <Tooltip | ||||||
|  |               label={hasCopied ? `${t('common.copied')}!` : `${t('common.copy')} ${t('keys.one')}`} | ||||||
|  |               hasArrow | ||||||
|  |               closeOnClick={false} | ||||||
|  |             > | ||||||
|  |               <IconButton | ||||||
|  |                 aria-label={t('common.copy')} | ||||||
|  |                 icon={<CopyIcon h={4} w={4} />} | ||||||
|  |                 onClick={onCopy} | ||||||
|  |                 size="xs" | ||||||
|  |                 colorScheme="teal" | ||||||
|  |                 ml={2} | ||||||
|  |               /> | ||||||
|  |             </Tooltip> | ||||||
|  |           </PopoverHeader> | ||||||
|  |           <PopoverBody> | ||||||
|  |             <Text whiteSpace="break-spaces"> | ||||||
|  |               <Center> | ||||||
|  |                 <pre style={{ fontFamily: 'monospace' }}>{apiKey.apiKey}</pre> | ||||||
|  |               </Center> | ||||||
|  |             </Text> | ||||||
|  |           </PopoverBody> | ||||||
|  |         </PopoverContent> | ||||||
|  |       </Popover> | ||||||
|  |     </HStack> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default ApiKeyActions; | ||||||
							
								
								
									
										131
									
								
								src/pages/Profile/ApiKeys/Table/AddButton.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								src/pages/Profile/ApiKeys/Table/AddButton.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,131 @@ | |||||||
|  | import * as React from 'react'; | ||||||
|  | import { | ||||||
|  |   Alert, | ||||||
|  |   AlertDescription, | ||||||
|  |   AlertIcon, | ||||||
|  |   Box, | ||||||
|  |   Button, | ||||||
|  |   FormControl, | ||||||
|  |   FormErrorMessage, | ||||||
|  |   FormLabel, | ||||||
|  |   Input, | ||||||
|  |   Textarea, | ||||||
|  |   useDisclosure, | ||||||
|  |   useToast, | ||||||
|  | } from '@chakra-ui/react'; | ||||||
|  | import axios from 'axios'; | ||||||
|  | import { useTranslation } from 'react-i18next'; | ||||||
|  | import ApiKeyExpiresOnField from './ExpiresOnField'; | ||||||
|  | import { CreateButton } from 'components/Buttons/CreateButton'; | ||||||
|  | import { Modal } from 'components/Modals/Modal'; | ||||||
|  | import { ApiKey, useCreateApiKey } from 'hooks/Network/ApiKeys'; | ||||||
|  |  | ||||||
|  | type Props = { | ||||||
|  |   apiKeys: ApiKey[]; | ||||||
|  |   userId: string; | ||||||
|  |   isDisabled?: boolean; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const thirtyDaysFromNow = () => Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30; | ||||||
|  |  | ||||||
|  | const CreateApiKeyButton = ({ apiKeys, userId, isDisabled }: Props) => { | ||||||
|  |   const { t } = useTranslation(); | ||||||
|  |   const toast = useToast(); | ||||||
|  |   const { isOpen, onOpen, onClose } = useDisclosure(); | ||||||
|  |   const [name, setName] = React.useState(''); | ||||||
|  |   const [description, setDescription] = React.useState(''); | ||||||
|  |   const [expiresOn, setExpiresOn] = React.useState(thirtyDaysFromNow()); | ||||||
|  |   const createKey = useCreateApiKey({ userId }); | ||||||
|  |  | ||||||
|  |   const onNameChange = (e: React.ChangeEvent<HTMLInputElement>) => setName(e.target.value); | ||||||
|  |   const onDescriptionChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => setDescription(e.target.value); | ||||||
|  |  | ||||||
|  |   const onCreate = React.useCallback(() => { | ||||||
|  |     createKey.mutate( | ||||||
|  |       { data: { name, description, userUuid: userId, expiresOn }, userId }, | ||||||
|  |       { | ||||||
|  |         onSuccess: () => { | ||||||
|  |           onClose(); | ||||||
|  |         }, | ||||||
|  |         onError: (e) => { | ||||||
|  |           if (axios.isAxiosError(e)) { | ||||||
|  |             toast({ | ||||||
|  |               id: 'create-api-key-error', | ||||||
|  |               title: t('common.error'), | ||||||
|  |               description: e?.response?.data?.ErrorDescription, | ||||||
|  |               status: 'error', | ||||||
|  |               duration: 5000, | ||||||
|  |               isClosable: true, | ||||||
|  |               position: 'top-right', | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     ); | ||||||
|  |   }, [name, description, expiresOn]); | ||||||
|  |  | ||||||
|  |   const nameError = React.useMemo(() => { | ||||||
|  |     if ( | ||||||
|  |       name.length === 0 || | ||||||
|  |       name.length > 20 || | ||||||
|  |       apiKeys.map(({ name: n }) => n).includes(name) || | ||||||
|  |       !/^[a-z0-9]+$/i.test(name) | ||||||
|  |     ) { | ||||||
|  |       return t('keys.name_error'); | ||||||
|  |     } | ||||||
|  |     return undefined; | ||||||
|  |   }, [name, apiKeys]); | ||||||
|  |  | ||||||
|  |   const handleOpenClick = () => { | ||||||
|  |     setName(''); | ||||||
|  |     setDescription(''); | ||||||
|  |     setExpiresOn(thirtyDaysFromNow()); | ||||||
|  |     onOpen(); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <> | ||||||
|  |       {apiKeys.length >= 10 && ( | ||||||
|  |         <Alert status="error"> | ||||||
|  |           <AlertIcon /> | ||||||
|  |           <AlertDescription>{t('keys.max_keys')}</AlertDescription> | ||||||
|  |         </Alert> | ||||||
|  |       )} | ||||||
|  |       <CreateButton onClick={handleOpenClick} isDisabled={isDisabled || apiKeys.length >= 10} isCompact /> | ||||||
|  |       <Modal | ||||||
|  |         isOpen={isOpen} | ||||||
|  |         onClose={onClose} | ||||||
|  |         title={` ${t('crud.create')} ${t('keys.one')}`} | ||||||
|  |         topRightButtons={ | ||||||
|  |           <Button | ||||||
|  |             colorScheme="blue" | ||||||
|  |             ml="1" | ||||||
|  |             onClick={onCreate} | ||||||
|  |             isDisabled={nameError !== undefined || description.length > 64} | ||||||
|  |           > | ||||||
|  |             {t('common.save')} | ||||||
|  |           </Button> | ||||||
|  |         } | ||||||
|  |         options={{ | ||||||
|  |           modalSize: 'sm', | ||||||
|  |         }} | ||||||
|  |       > | ||||||
|  |         <Box> | ||||||
|  |           <FormControl mb={2} isInvalid={nameError !== undefined}> | ||||||
|  |             <FormLabel>{t('common.name')}</FormLabel> | ||||||
|  |             <Input value={name} onChange={onNameChange} /> | ||||||
|  |             <FormErrorMessage>{nameError}</FormErrorMessage> | ||||||
|  |           </FormControl> | ||||||
|  |           <FormControl mb={2} isInvalid={description.length > 64}> | ||||||
|  |             <FormLabel>{t('common.description')}</FormLabel> | ||||||
|  |             <Textarea value={description} onChange={onDescriptionChange} noOfLines={2} /> | ||||||
|  |             <FormErrorMessage>{t('keys.description_error')}</FormErrorMessage> | ||||||
|  |           </FormControl> | ||||||
|  |           <ApiKeyExpiresOnField value={expiresOn} setValue={setExpiresOn} /> | ||||||
|  |         </Box> | ||||||
|  |       </Modal> | ||||||
|  |     </> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default CreateApiKeyButton; | ||||||
							
								
								
									
										123
									
								
								src/pages/Profile/ApiKeys/Table/DescriptionCell.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								src/pages/Profile/ApiKeys/Table/DescriptionCell.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | |||||||
|  | import * as React from 'react'; | ||||||
|  | import { | ||||||
|  |   Box, | ||||||
|  |   Button, | ||||||
|  |   Center, | ||||||
|  |   FormControl, | ||||||
|  |   FormErrorMessage, | ||||||
|  |   FormLabel, | ||||||
|  |   IconButton, | ||||||
|  |   Popover, | ||||||
|  |   PopoverArrow, | ||||||
|  |   PopoverBody, | ||||||
|  |   PopoverCloseButton, | ||||||
|  |   PopoverContent, | ||||||
|  |   PopoverFooter, | ||||||
|  |   PopoverHeader, | ||||||
|  |   PopoverTrigger, | ||||||
|  |   Text, | ||||||
|  |   Textarea, | ||||||
|  |   Tooltip, | ||||||
|  |   useDisclosure, | ||||||
|  |   useToast, | ||||||
|  | } from '@chakra-ui/react'; | ||||||
|  | import axios from 'axios'; | ||||||
|  | import { Pen } from 'phosphor-react'; | ||||||
|  | import { useTranslation } from 'react-i18next'; | ||||||
|  | import { ApiKey, useUpdateApiKey } from 'hooks/Network/ApiKeys'; | ||||||
|  |  | ||||||
|  | type Props = { | ||||||
|  |   apiKey: ApiKey; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const ApiKeyDescriptionCell = ({ apiKey }: Props) => { | ||||||
|  |   const { t } = useTranslation(); | ||||||
|  |   const toast = useToast(); | ||||||
|  |   const [newDescription, setNewDescription] = React.useState(apiKey.description); | ||||||
|  |   const { isOpen, onOpen, onClose } = useDisclosure(); | ||||||
|  |   const updateApiKey = useUpdateApiKey({ userId: apiKey.userUuid }); | ||||||
|  |  | ||||||
|  |   const onDescriptionChange = React.useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => { | ||||||
|  |     setNewDescription(e.target.value); | ||||||
|  |   }, []); | ||||||
|  |  | ||||||
|  |   const handleSaveClick = () => { | ||||||
|  |     updateApiKey.mutate( | ||||||
|  |       { | ||||||
|  |         userId: apiKey.userUuid, | ||||||
|  |         keyId: apiKey.id, | ||||||
|  |         description: newDescription, | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         onSuccess: () => { | ||||||
|  |           onClose(); | ||||||
|  |         }, | ||||||
|  |         onError: (e) => { | ||||||
|  |           if (axios.isAxiosError(e)) { | ||||||
|  |             toast({ | ||||||
|  |               id: 'error-save-api-key-description', | ||||||
|  |               title: t('common.error'), | ||||||
|  |               description: e?.response?.data?.ErrorDescription, | ||||||
|  |               status: 'error', | ||||||
|  |               duration: 5000, | ||||||
|  |               isClosable: true, | ||||||
|  |               position: 'top-right', | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     ); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const onCancel = () => { | ||||||
|  |     setNewDescription(apiKey.description); | ||||||
|  |     onClose(); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <Text w="100%" overflowWrap="break-word" whiteSpace="pre-wrap"> | ||||||
|  |       <Popover isOpen={isOpen} onOpen={onOpen} onClose={onClose}> | ||||||
|  |         <Tooltip label={t('common.edit')} hasArrow closeOnClick={false} isDisabled={isOpen}> | ||||||
|  |           <Box display="unset"> | ||||||
|  |             <PopoverTrigger> | ||||||
|  |               <IconButton aria-label={t('common.edit')} icon={<Pen size={20} />} size="xs" colorScheme="teal" mr={2} /> | ||||||
|  |             </PopoverTrigger> | ||||||
|  |           </Box> | ||||||
|  |         </Tooltip> | ||||||
|  |         <PopoverContent w="340px"> | ||||||
|  |           <PopoverArrow /> | ||||||
|  |           <PopoverCloseButton /> | ||||||
|  |           <PopoverHeader> | ||||||
|  |             {t('common.edit')} {apiKey.name} {t('common.description')} | ||||||
|  |           </PopoverHeader> | ||||||
|  |           <PopoverBody> | ||||||
|  |             <FormControl mb={2} isInvalid={newDescription.length > 64}> | ||||||
|  |               <FormLabel>{t('common.description')}</FormLabel> | ||||||
|  |               <Textarea value={newDescription} onChange={onDescriptionChange} noOfLines={2} /> | ||||||
|  |               <FormErrorMessage>{t('keys.description_error')}</FormErrorMessage> | ||||||
|  |             </FormControl> | ||||||
|  |           </PopoverBody> | ||||||
|  |           <PopoverFooter> | ||||||
|  |             <Center> | ||||||
|  |               <Button colorScheme="gray" mr="1" onClick={onCancel}> | ||||||
|  |                 {t('common.cancel')} | ||||||
|  |               </Button> | ||||||
|  |               <Button | ||||||
|  |                 colorScheme="blue" | ||||||
|  |                 ml="1" | ||||||
|  |                 onClick={handleSaveClick} | ||||||
|  |                 isDisabled={newDescription.length > 64} | ||||||
|  |                 isLoading={updateApiKey.isLoading} | ||||||
|  |               > | ||||||
|  |                 {t('common.save')} | ||||||
|  |               </Button> | ||||||
|  |             </Center> | ||||||
|  |           </PopoverFooter> | ||||||
|  |         </PopoverContent> | ||||||
|  |       </Popover> | ||||||
|  |       {apiKey.description} | ||||||
|  |     </Text> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default ApiKeyDescriptionCell; | ||||||
							
								
								
									
										81
									
								
								src/pages/Profile/ApiKeys/Table/ExpiresOnField.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								src/pages/Profile/ApiKeys/Table/ExpiresOnField.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | |||||||
|  | import * as React from 'react'; | ||||||
|  | import { Box, Flex, FormControl, FormErrorMessage, FormLabel, Radio, RadioGroup, Stack, Text } from '@chakra-ui/react'; | ||||||
|  | import { useTranslation } from 'react-i18next'; | ||||||
|  | import DateTimePicker from 'components/DatePickers/DateTimePicker'; | ||||||
|  |  | ||||||
|  | type Props = { | ||||||
|  |   value: number; | ||||||
|  |   setValue: (value: number) => void; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const aYearFromNow = () => Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 365; | ||||||
|  |  | ||||||
|  | const ApiKeyExpiresOnField = ({ value, setValue }: Props) => { | ||||||
|  |   const { t } = useTranslation(); | ||||||
|  |   const [radio, setRadioValue] = React.useState('30'); | ||||||
|  |  | ||||||
|  |   const onRadioChange = (v: string) => { | ||||||
|  |     setRadioValue(v); | ||||||
|  |     const days = parseInt(v, 10); | ||||||
|  |     const now = new Date(); | ||||||
|  |     if (days > 0) { | ||||||
|  |       now.setDate(now.getDate() + days); | ||||||
|  |       setValue(Math.floor(now.getTime() / 1000)); | ||||||
|  |     } else { | ||||||
|  |       now.setDate(now.getDate() + 30); | ||||||
|  |       setValue(Math.floor(now.getTime() / 1000)); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const onDateChange = (v: Date | null) => { | ||||||
|  |     if (v) setValue(Math.floor(v.getTime() / 1000)); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const tempDate = () => { | ||||||
|  |     if (!value || value === 0) return new Date(); | ||||||
|  |  | ||||||
|  |     return new Date(value * 1000); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <FormControl isInvalid={value > aYearFromNow()} isRequired> | ||||||
|  |       <FormLabel ms={0} mb={2} fontSize="md" fontWeight="normal" _disabled={{ opacity: 0.8 }}> | ||||||
|  |         {t('certificates.expires_on')} | ||||||
|  |       </FormLabel> | ||||||
|  |       <Box> | ||||||
|  |         <RadioGroup onChange={onRadioChange} defaultValue="30"> | ||||||
|  |           <Stack spacing={5} direction="column"> | ||||||
|  |             <Radio colorScheme="blue" value="30"> | ||||||
|  |               30 {t('common.days')} | ||||||
|  |             </Radio> | ||||||
|  |             <Radio colorScheme="blue" value="60"> | ||||||
|  |               60 {t('common.days')} | ||||||
|  |             </Radio> | ||||||
|  |             <Radio colorScheme="blue" value="90"> | ||||||
|  |               90 {t('common.days')} | ||||||
|  |             </Radio> | ||||||
|  |             <Radio colorScheme="blue" value="180"> | ||||||
|  |               6 {t('common.months')} | ||||||
|  |             </Radio> | ||||||
|  |             <Radio colorScheme="green" value="0"> | ||||||
|  |               <Flex> | ||||||
|  |                 <Text my="auto" mr={2} w="180px"> | ||||||
|  |                   {t('common.custom')} | ||||||
|  |                 </Text> | ||||||
|  |                 <DateTimePicker | ||||||
|  |                   date={tempDate()} | ||||||
|  |                   isStart | ||||||
|  |                   onChange={onDateChange} | ||||||
|  |                   isDisabled={!value || value === 0 || radio !== '0'} | ||||||
|  |                 /> | ||||||
|  |               </Flex> | ||||||
|  |             </Radio> | ||||||
|  |           </Stack> | ||||||
|  |         </RadioGroup> | ||||||
|  |       </Box> | ||||||
|  |       <FormErrorMessage>{t('keys.expire_error')}</FormErrorMessage> | ||||||
|  |     </FormControl> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default ApiKeyExpiresOnField; | ||||||
							
								
								
									
										54
									
								
								src/pages/Profile/ApiKeys/Table/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/pages/Profile/ApiKeys/Table/index.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | import * as React from 'react'; | ||||||
|  | import { Box, Flex, Heading, HStack, Spacer } from '@chakra-ui/react'; | ||||||
|  | import { useTranslation } from 'react-i18next'; | ||||||
|  | import CreateApiKeyButton from './AddButton'; | ||||||
|  | import useApiKeyTable from './useApiKeyTable'; | ||||||
|  | import { RefreshButton } from 'components/Buttons/RefreshButton'; | ||||||
|  | import { ColumnPicker } from 'components/DataTables/ColumnPicker'; | ||||||
|  | import { DataTable } from 'components/DataTables/DataTable'; | ||||||
|  | import { Column } from 'models/Table'; | ||||||
|  |  | ||||||
|  | type Props = { | ||||||
|  |   userId: string; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const ApiKeyTable = ({ userId }: Props) => { | ||||||
|  |   const { t } = useTranslation(); | ||||||
|  |   const { query, columns, hiddenColumns } = useApiKeyTable({ userId }); | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <Box> | ||||||
|  |       <Flex mb={2}> | ||||||
|  |         <Heading size="md" my="auto"> | ||||||
|  |           {t('keys.other')} ({query.data?.apiKeys.length}) | ||||||
|  |         </Heading> | ||||||
|  |         <Spacer /> | ||||||
|  |         <HStack spacing={2}> | ||||||
|  |           <CreateApiKeyButton userId={userId} apiKeys={query.data?.apiKeys ?? []} /> | ||||||
|  |           <ColumnPicker | ||||||
|  |             columns={columns as Column<unknown>[]} | ||||||
|  |             hiddenColumns={hiddenColumns[0]} | ||||||
|  |             setHiddenColumns={hiddenColumns[1]} | ||||||
|  |             preference="apiKeys.profile.table.hiddenColumns" | ||||||
|  |           /> | ||||||
|  |           <RefreshButton onClick={query.refetch} isFetching={query.isFetching} isCompact /> | ||||||
|  |         </HStack> | ||||||
|  |       </Flex> | ||||||
|  |       <Box> | ||||||
|  |         <DataTable | ||||||
|  |           columns={columns as Column<object>[]} | ||||||
|  |           saveSettingsId="apiKeys.profile.table" | ||||||
|  |           data={query.data?.apiKeys ?? []} | ||||||
|  |           obj={t('keys.other')} | ||||||
|  |           sortBy={[{ id: 'expiresOn', desc: false }]} | ||||||
|  |           minHeight="400px" | ||||||
|  |           hiddenColumns={hiddenColumns[0]} | ||||||
|  |           showAllRows | ||||||
|  |           hideControls | ||||||
|  |         /> | ||||||
|  |       </Box> | ||||||
|  |     </Box> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default ApiKeyTable; | ||||||
							
								
								
									
										78
									
								
								src/pages/Profile/ApiKeys/Table/useApiKeyTable.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/pages/Profile/ApiKeys/Table/useApiKeyTable.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | |||||||
|  | import * as React from 'react'; | ||||||
|  | import { useTranslation } from 'react-i18next'; | ||||||
|  | import ApiKeyActions from './Actions'; | ||||||
|  | import ApiKeyDescriptionCell from './DescriptionCell'; | ||||||
|  | import FormattedDate from 'components/InformationDisplays/FormattedDate'; | ||||||
|  | import { ApiKey, useGetUserApiKeys } from 'hooks/Network/ApiKeys'; | ||||||
|  | import { Column } from 'models/Table'; | ||||||
|  |  | ||||||
|  | type Props = { | ||||||
|  |   userId: string; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const useApiKeyTable = ({ userId }: Props) => { | ||||||
|  |   const { t } = useTranslation(); | ||||||
|  |   const hiddenColumns = React.useState<string[]>([]); | ||||||
|  |   const query = useGetUserApiKeys({ userId }); | ||||||
|  |  | ||||||
|  |   const dateCell = React.useCallback((date: number) => <FormattedDate date={date} />, []); | ||||||
|  |   const actionCell = React.useCallback((apiKey: ApiKey) => <ApiKeyActions apiKey={apiKey} />, []); | ||||||
|  |   const descriptionCell = React.useCallback((apiKey: ApiKey) => <ApiKeyDescriptionCell apiKey={apiKey} />, []); | ||||||
|  |  | ||||||
|  |   const columns = React.useMemo( | ||||||
|  |     (): Column<ApiKey>[] => [ | ||||||
|  |       { | ||||||
|  |         id: 'name', | ||||||
|  |         Header: t('common.name'), | ||||||
|  |         Footer: '', | ||||||
|  |         accessor: 'name', | ||||||
|  |         alwaysShow: true, | ||||||
|  |         customWidth: '120px', | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         id: 'expiresOn', | ||||||
|  |         Header: t('keys.expires'), | ||||||
|  |         Footer: '', | ||||||
|  |         Cell: ({ cell }) => dateCell(cell.row.original.expiresOn), | ||||||
|  |         accessor: 'expiresOn', | ||||||
|  |         customWidth: '120px', | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         id: 'lastUse', | ||||||
|  |         Header: t('common.last_use'), | ||||||
|  |         Footer: '', | ||||||
|  |         Cell: ({ cell }) => dateCell(cell.row.original.lastUse), | ||||||
|  |         accessor: 'lastUse', | ||||||
|  |         customWidth: '120px', | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         id: 'description', | ||||||
|  |         Header: t('common.description'), | ||||||
|  |         Footer: '', | ||||||
|  |         Cell: ({ cell }) => descriptionCell(cell.row.original), | ||||||
|  |         accessor: 'description', | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         id: 'actions', | ||||||
|  |         Header: t('common.actions'), | ||||||
|  |         Footer: '', | ||||||
|  |         Cell: (v) => actionCell(v.cell.row.original), | ||||||
|  |         disableSortBy: true, | ||||||
|  |         customWidth: '120px', | ||||||
|  |         alwaysShow: true, | ||||||
|  |       }, | ||||||
|  |     ], | ||||||
|  |     [t], | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   return React.useMemo( | ||||||
|  |     () => ({ | ||||||
|  |       query, | ||||||
|  |       columns, | ||||||
|  |       hiddenColumns, | ||||||
|  |     }), | ||||||
|  |     [query, columns, hiddenColumns], | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default useApiKeyTable; | ||||||
							
								
								
									
										19
									
								
								src/pages/Profile/ApiKeys/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/pages/Profile/ApiKeys/index.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | import * as React from 'react'; | ||||||
|  | import ApiKeyTable from './Table'; | ||||||
|  | import { Card } from 'components/Containers/Card'; | ||||||
|  | import { CardBody } from 'components/Containers/Card/CardBody'; | ||||||
|  | import { useAuth } from 'contexts/AuthProvider'; | ||||||
|  |  | ||||||
|  | const ApiKeysCard = () => { | ||||||
|  |   const { user } = useAuth(); | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <Card p={4}> | ||||||
|  |       <CardBody> | ||||||
|  |         <ApiKeyTable userId={user?.id ?? ''} /> | ||||||
|  |       </CardBody> | ||||||
|  |     </Card> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default ApiKeysCard; | ||||||
| @@ -1,5 +1,6 @@ | |||||||
| import * as React from 'react'; | import * as React from 'react'; | ||||||
| import Masonry from 'react-masonry-css'; | import Masonry from 'react-masonry-css'; | ||||||
|  | import ApiKeysCard from './ApiKeys'; | ||||||
| import GeneralInformationProfile from './GeneralInformation'; | import GeneralInformationProfile from './GeneralInformation'; | ||||||
| import MultiFactorAuthProfile from './MultiFactorAuth'; | import MultiFactorAuthProfile from './MultiFactorAuth'; | ||||||
| import ProfileNotes from './Notes'; | import ProfileNotes from './Notes'; | ||||||
| @@ -10,7 +11,7 @@ const ProfileLayout = () => ( | |||||||
|     breakpointCols={{ |     breakpointCols={{ | ||||||
|       default: 3, |       default: 3, | ||||||
|       1800: 2, |       1800: 2, | ||||||
|       1100: 1, |       1200: 1, | ||||||
|     }} |     }} | ||||||
|     className="my-masonry-grid" |     className="my-masonry-grid" | ||||||
|     columnClassName="my-masonry-grid_column" |     columnClassName="my-masonry-grid_column" | ||||||
| @@ -19,6 +20,7 @@ const ProfileLayout = () => ( | |||||||
|     <MultiFactorAuthProfile /> |     <MultiFactorAuthProfile /> | ||||||
|     <GeneralInformationProfile /> |     <GeneralInformationProfile /> | ||||||
|     <ProfileNotes /> |     <ProfileNotes /> | ||||||
|  |     <ApiKeysCard /> | ||||||
|   </Masonry> |   </Masonry> | ||||||
| ); | ); | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Charles
					Charles