Merge branch 'develop' into feature/1484-star-prompt

This commit is contained in:
Alexander Holliday authored and GitHub committed 2025-03-11 12:40:46 -07:00
commit 32e35b998a
40 files changed
+1144 -134

No files matched your search

+89
View File
@@ -0,0 +1,89 @@
import axios from 'axios';
import fs from 'fs-extra';
import path from 'path';
import { URLSearchParams } from 'url';
// POEditor API information
const API_TOKEN = process.env.POEDITOR_API;
const PROJECT_ID = process.env.POEDITOR_PROJECT_ID;
const LANGUAGES = (process.env.LANGUAGES || 'tr,en').split(',');
const EXPORT_FORMAT = process.env.EXPORT_FORMAT || 'key_value_json';
// POEditor API endpoint
const API_URL = 'https://api.poeditor.com/v2';
// Function to download translations
async function downloadTranslations() {
try {
console.log('Downloading translations from POEditor...');
console.log(`Using export format: ${EXPORT_FORMAT}`);
for (const language of LANGUAGES) {
console.log(`Downloading translations for ${language} language...`);
// Get export URL from POEditor
const exportResponse = await axios.post(`${API_URL}/projects/export`,
new URLSearchParams({
api_token: API_TOKEN,
id: PROJECT_ID,
language: language,
type: EXPORT_FORMAT
})
);
if (exportResponse.data.response.status !== 'success') {
throw new Error(`Failed to get export URL for ${language} language: ${JSON.stringify(exportResponse.data)}`);
}
const fileUrl = exportResponse.data.result.url;
console.log(`Export URL obtained for ${language}`);
// Download translation file
const downloadResponse = await axios.get(fileUrl, { responseType: 'json' });
const translations = downloadResponse.data;
console.log(`Downloaded translations for ${language}`);
// Check the format of data returned from POEditor and convert if necessary
let formattedTranslations = translations;
// If data is in array format, convert it to key-value format
if (Array.isArray(translations)) {
console.log(`Converting array format to key-value format for ${language}`);
formattedTranslations = {};
translations.forEach(item => {
if (item.term && item.definition) {
formattedTranslations[item.term] = item.definition;
}
});
}
// Save file
const outputPath = path.join(process.cwd(), 'temp', `${language}.json`);
await fs.writeJson(outputPath, formattedTranslations, { spaces: 2 });
console.log(`Translations for ${language} language successfully downloaded and saved: ${outputPath}`);
}
console.log('All translations successfully downloaded!');
} catch (error) {
console.error('An error occurred while downloading translations:', error);
process.exit(1);
}
}
// Main function
async function main() {
try {
// Clean temp folder
await fs.emptyDir(path.join(process.cwd(), 'temp'));
// Download translations
await downloadTranslations();
} catch (error) {
console.error('An error occurred during the process:', error);
process.exit(1);
}
}
// Run script
main();
+41
View File
@@ -0,0 +1,41 @@
# POEditor Translation Synchronization
This GitHub Actions workflow automatically downloads translation files from POEditor and integrates them into the project.
## How It Works
The workflow can be triggered in two ways:
1. **Manual Trigger**: You can manually run the "POEditor Translation Synchronization" workflow from the "Actions" tab in the GitHub interface.
2. **Automatic Trigger**: The workflow runs automatically every day at midnight (UTC).
## Required Settings
For this workflow to function, you need to define the following secrets in your GitHub repository:
1. `POEDITOR_API_TOKEN`: Your POEditor API token
2. `POEDITOR_PROJECT_ID`: Your POEditor project ID
You can add these secrets in the "Settings > Secrets and variables > Actions" section of your GitHub repository.
## Manual Execution
When running the workflow manually, you can specify which languages to download. Languages should be entered as comma-separated values (e.g., `tr,gb,es`).
If you don't specify any languages, the default languages `tr` and `en` will be downloaded.
## Output
When the workflow completes successfully:
1. Translation files for the specified languages are downloaded from POEditor
2. These files are copied to the `src/locales/` directory
3. Changes are automatically committed and pushed to the main branch
## Troubleshooting
If the workflow fails:
1. Check the GitHub Actions logs
2. Make sure your POEditor API token and project ID are correct
3. Ensure that the languages you specified exist in your POEditor project
@@ -0,0 +1,9 @@
- name: First interaction
uses: actions/first-interaction@v1.3.0
with:
# Token for the repository. Can be passed in using {{ secrets.GITHUB_TOKEN }}
repo-token:
# Comment to post on an individual's first issue
issue-message: Thanks for opening your first issue! :) # optional
# Comment to post on an individual's first pull request
pr-message: Thanks for creating your first PR! :) # optional
+92
View File
@@ -0,0 +1,92 @@
name: POEditor Translation Synchronization
on:
# For manual triggering
workflow_dispatch:
inputs:
languages:
description: "Languages to synchronize (comma separated, e.g.: tr,en,es)"
required: false
default: "tr,en"
format:
description: "Export format (key_value_json or json)"
required: false
default: "key_value_json"
# For automatic execution at a specific time (every day at midnight)
schedule:
- cron: "0 0 * * *"
jobs:
sync-translations:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: "18"
- name: Create package.json for scripts
run: |
mkdir -p .github/scripts
cat > .github/scripts/package.json << EOF
{
"name": "poeditor-scripts",
"version": "1.0.0",
"private": true,
"type": "module",
"dependencies": {
"axios": "^1.6.0",
"fs-extra": "^11.1.1"
}
}
EOF
- name: Install dependencies
run: |
cd .github/scripts
npm install
- name: Download translations from POEditor
env:
POEDITOR_API: ${{ secrets.POEDITOR_API }}
POEDITOR_PROJECT_ID: ${{ secrets.POEDITOR_PROJECT_ID }}
LANGUAGES: ${{ github.event.inputs.languages || 'tr,en' }}
EXPORT_FORMAT: ${{ github.event.inputs.format || 'key_value_json' }}
run: |
mkdir -p temp
node .github/scripts/download-translations.js
- name: Verify translation files
run: |
echo "Verifying translation files..."
for file in temp/*.json; do
echo "Checking $file"
if [ ! -s "$file" ]; then
echo "Error: $file is empty or does not exist"
exit 1
fi
# Validate JSON format
cat "$file" | jq . > /dev/null || { echo "Error: $file is not valid JSON"; exit 1; }
done
echo "All translation files are valid"
- name: Copy translations to project
run: |
mkdir -p src/locales
cp -r temp/* src/locales/
echo "Translation files copied to src/locales/"
- name: Commit changes
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add src/locales/*.json
git diff --staged --quiet || git commit -m "Translations updated from POEditor"
git push
+4 -4
View File
@@ -18,7 +18,7 @@
"@mui/x-charts": "^7.5.1",
"@mui/x-data-grid": "7.27.3",
"@mui/x-date-pickers": "7.27.3",
"@reduxjs/toolkit": "2.6.0",
"@reduxjs/toolkit": "2.6.1",
"@solana/wallet-adapter-base": "0.9.23",
"@solana/wallet-adapter-material-ui": "0.16.34",
"@solana/wallet-adapter-react": "0.15.35",
@@ -4692,9 +4692,9 @@
}
},
"node_modules/@reduxjs/toolkit": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.6.0.tgz",
"integrity": "sha512-mWJCYpewLRyTuuzRSEC/IwIBBkYg2dKtQas8mty5MaV2iXzcmicS3gW554FDeOvLnY3x13NIk8MB1e8wHO7rqQ==",
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.6.1.tgz",
"integrity": "sha512-SSlIqZNYhqm/oMkXbtofwZSt9lrncblzo6YcZ9zoX+zLngRBrCOjK4lNLdkNucJF58RHOWrD9txT3bT3piH7Zw==",
"license": "MIT",
"dependencies": {
"immer": "^10.0.3",
+1 -1
View File
@@ -21,7 +21,7 @@
"@mui/icons-material": "6.4.7",
"@mui/lab": "6.0.0-beta.30",
"@mui/material": "6.4.7",
"@reduxjs/toolkit": "2.6.0",
"@reduxjs/toolkit": "2.6.1",
"@solana/wallet-adapter-base": "0.9.23",
"@solana/wallet-adapter-material-ui": "0.16.34",
"@solana/wallet-adapter-react": "0.15.35",
+3 -3
View File
@@ -128,7 +128,7 @@ const Search = ({
<Typography
component="span"
className="input-error"
color={theme.palette.error.contrastText}
color={theme.palette.error.main}
mt={theme.spacing(2)}
sx={{
opacity: 0.8,
@@ -188,8 +188,8 @@ const Search = ({
color: "red",
},
"& li.MuiAutocomplete-option:hover:not([aria-selected='true'])": {
color: theme.palette.primary.main,
backgroundColor: theme.palette.primary.contrastTextSecondary,
color: theme.palette.secondary.contrastText,
backgroundColor: theme.palette.secondary.main,
},
"& .MuiAutocomplete-noOptions": {
px: theme.spacing(6),
+4 -2
View File
@@ -114,7 +114,7 @@ ColoredLabel.propTypes = {
/**
* @component
* @param {Object} props
* @param { 'up' | 'down' | 'cannot resolve'} props.status - The status for the label
* @param {'up' | 'down' | 'paused' | 'pending' | 'cannot resolve' | 'published' | 'unpublished'} props.status - The status for the label
* @param {string} props.text - The text of the label
* @returns {JSX.Element}
* @example
@@ -128,6 +128,8 @@ const statusToTheme = {
paused: "warning",
pending: "warning",
"cannot resolve": "error",
published: "success",
unpublished: "error",
};
const StatusLabel = ({ status, text, customStyles }) => {
@@ -156,7 +158,7 @@ const StatusLabel = ({ status, text, customStyles }) => {
};
StatusLabel.propTypes = {
status: PropTypes.oneOf(["up", "down", "paused", "pending", "cannot resolve"]),
status: PropTypes.oneOf(["up", "down", "paused", "pending", "cannot resolve", "published", "unpublished"]),
text: PropTypes.string,
customStyles: PropTypes.object,
};
+8 -1
View File
@@ -14,7 +14,14 @@ const Link = ({ level, label, url }) => {
const theme = useTheme();
const levelConfig = {
primary: {},
primary: {
color: theme.palette.primary.contrastTextTertiary,
sx: {
":hover": {
color: theme.palette.primary.contrastTextSecondary,
},
},
},
secondary: {
color: theme.palette.primary.contrastTextSecondary,
sx: {
@@ -0,0 +1,273 @@
import { useState, useMemo } from "react";
import { useTranslation } from "react-i18next";
import {
Dialog,
DialogContent,
DialogActions,
Button,
Typography,
Box,
Tabs,
Tab
} from "@mui/material";
import { useTheme } from "@emotion/react";
import TabPanel from "./TabPanel";
import TabComponent from "./TabComponent";
const NotificationIntegrationModal = ({
open,
onClose,
monitor,
setMonitor,
// Optional prop to configure available notification types
notificationTypes = null
}) => {
const { t } = useTranslation();
const theme = useTheme();
const [tabValue, setTabValue] = useState(0);
// Define notification types
const DEFAULT_NOTIFICATION_TYPES = [
{
id: 'slack',
label: t('notifications.slack.label'),
description: t('notifications.slack.description'),
fields: [
{
id: 'webhook',
label: t('notifications.slack.webhookLabel'),
placeholder: t('notifications.slack.webhookPlaceholder'),
type: 'text'
}
]
},
{
id: 'discord',
label: t('notifications.discord.label'),
description: t('notifications.discord.description'),
fields: [
{
id: 'webhook',
label: t('notifications.discord.webhookLabel'),
placeholder: t('notifications.discord.webhookPlaceholder'),
type: 'text'
}
]
},
{
id: 'telegram',
label: t('notifications.telegram.label'),
description: t('notifications.telegram.description'),
fields: [
{
id: 'token',
label: t('notifications.telegram.tokenLabel'),
placeholder: t('notifications.telegram.tokenPlaceholder'),
type: 'text'
},
{
id: 'chatId',
label: t('notifications.telegram.chatIdLabel'),
placeholder: t('notifications.telegram.chatIdPlaceholder'),
type: 'text'
}
]
},
{
id: 'webhook',
label: t('notifications.webhook.label'),
description: t('notifications.webhook.description'),
fields: [
{
id: 'url',
label: t('notifications.webhook.urlLabel'),
placeholder: t('notifications.webhook.urlPlaceholder'),
type: 'text'
}
]
}
];
// Use provided notification types or default to our translated ones
const activeNotificationTypes = notificationTypes || DEFAULT_NOTIFICATION_TYPES;
// Memoized function to initialize integrations state
const initialIntegrationsState = useMemo(() => {
const state = {};
activeNotificationTypes.forEach(type => {
// Add enabled flag for each notification type
state[type.id] = monitor?.notifications?.some(n => n.type === type.id) || false;
// Add state for each field in the notification type
type.fields.forEach(field => {
const fieldKey = `${type.id}${field.id.charAt(0).toUpperCase() + field.id.slice(1)}`;
state[fieldKey] = monitor?.notifications?.find(n => n.type === type.id)?.[field.id] || "";
});
});
return state;
}, [monitor, activeNotificationTypes]); // Only recompute when these dependencies change
const [integrations, setIntegrations] = useState(initialIntegrationsState);
const handleChangeTab = (event, newValue) => {
setTabValue(newValue);
};
const handleIntegrationChange = (type, checked) => {
setIntegrations(prev => ({
...prev,
[type]: checked
}));
};
const handleInputChange = (type, value) => {
setIntegrations(prev => ({
...prev,
[type]: value
}));
};
const handleTestNotification = (type) => {
console.log(`Testing ${type} notification`);
//implement the test notification functionality
};
const handleSave = () => {
//notifications array for selected integrations
const notifications = [...(monitor?.notifications || [])];
// Get all notification types IDs
const existingTypes = activeNotificationTypes.map(type => type.id);
// Filter out notifications that are configurable in this modal
const filteredNotifications = notifications.filter(
notification => !existingTypes.includes(notification.type)
);
// Add each enabled notification with its configured fields
activeNotificationTypes.forEach(type => {
if (integrations[type.id]) {
const notificationObject = {
type: type.id
};
// Add each field value to the notification object
type.fields.forEach(field => {
const fieldKey = `${type.id}${field.id.charAt(0).toUpperCase() + field.id.slice(1)}`;
notificationObject[field.id] = integrations[fieldKey];
});
filteredNotifications.push(notificationObject);
}
});
// Update monitor with new notifications
setMonitor(prev => ({
...prev,
notifications: filteredNotifications
}));
onClose();
};
return (
<Dialog
open={open}
onClose={onClose}
fullWidth
maxWidth="md"
sx={{
'& .MuiDialog-paper': {
width: `calc(80% - ${theme.spacing(40)})`,
maxWidth: `${theme.breakpoints.values.md - 70}px`
}
}}
>
<DialogContent>
<Box sx={{
display: 'flex',
height: `calc(26vh - ${theme.spacing(20)})`
}}>
{/* Left sidebar with tabs */}
<Box sx={{
borderRight: 1,
borderColor: theme.palette.primary.lowContrast,
width: '30%',
maxWidth: theme.spacing(120),
pr: theme.spacing(10)
}}>
<Typography variant="subtitle1" sx={{
my: theme.spacing(1),
fontWeight: 'bold',
fontSize: theme.typography.fontSize * 0.9,
color: theme.palette.primary.contrastTextSecondary,
pl: theme.spacing(4)
}}>
{t('notifications.addOrEditNotifications')}
</Typography>
<Tabs
orientation="vertical"
variant="scrollable"
value={tabValue}
onChange={handleChangeTab}
aria-label="Notification tabs"
>
{activeNotificationTypes.map((type) => (
<Tab
key={type.id}
label={type.label}
orientation="vertical"
disableRipple
/>
))}
</Tabs>
</Box>
{/* Right side content */}
<Box sx={{
flex: 1,
pl: theme.spacing(7.5),
overflowY: 'auto'
}}>
{activeNotificationTypes.map((type, index) => (
<TabPanel key={type.id} value={tabValue} index={index}>
<TabComponent
type={type}
integrations={integrations}
handleIntegrationChange={handleIntegrationChange}
handleInputChange={handleInputChange}
handleTestNotification={handleTestNotification}
/>
</TabPanel>
))}
</Box>
</Box>
</DialogContent>
<DialogActions sx={{
p: theme.spacing(4),
display: 'flex',
justifyContent: 'flex-end',
mb: theme.spacing(5),
mr: theme.spacing(5)
}}>
<Button
variant="contained"
color="accent"
onClick={handleSave}
sx={{
width: 'auto',
minWidth: theme.spacing(60),
px: theme.spacing(8)
}}
>
{t('common.save', 'Save')}
</Button>
</DialogActions>
</Dialog>
);
};
export default NotificationIntegrationModal;
@@ -0,0 +1,100 @@
import React from "react";
import {
Typography,
Box,
Button
} from "@mui/material";
import { useTranslation } from "react-i18next";
import { useTheme } from "@emotion/react";
import TextInput from "../../../src/Components/Inputs/TextInput";
import Checkbox from "../../../src/Components/Inputs/Checkbox";
const TabComponent = ({
type,
integrations,
handleIntegrationChange,
handleInputChange,
handleTestNotification
}) => {
const theme = useTheme();
const { t } = useTranslation();
// Helper to get the field state key (e.g., slackWebhook, telegramToken)
const getFieldKey = (typeId, fieldId) => {
return `${typeId}${fieldId.charAt(0).toUpperCase() + fieldId.slice(1)}`;
};
// Check if all fields have values to enable test button
const areAllFieldsFilled = () => {
return type.fields.every(field => {
const fieldKey = getFieldKey(type.id, field.id);
return integrations[fieldKey];
});
};
return (
<>
<Typography variant="subtitle1" component="h4" sx={{
fontWeight: 'bold',
color: theme.palette.primary.contrastTextSecondary
}}>
{type.label}
</Typography>
<Typography sx={{
mt: theme.spacing(0.5),
mb: theme.spacing(1.5),
color: theme.palette.primary.contrastTextTertiary
}}>
{type.description}
</Typography>
<Box sx={{ pl: theme.spacing(1.5) }}>
<Checkbox
id={`enable-${type.id}`}
label={t('notifications.enableNotifications', { platform: type.label })}
isChecked={integrations[type.id]}
onChange={(e) => handleIntegrationChange(type.id, e.target.checked)}
/>
</Box>
{type.fields.map(field => {
const fieldKey = getFieldKey(type.id, field.id);
return (
<Box key={field.id} sx={{ mt: theme.spacing(1) }}>
<Typography sx={{
mb: theme.spacing(2),
fontWeight: 'bold',
color: theme.palette.primary.contrastTextSecondary
}}>
{field.label}
</Typography>
<TextInput
id={`${type.id}-${field.id}`}
type={field.type}
placeholder={field.placeholder}
value={integrations[fieldKey]}
onChange={(e) => handleInputChange(fieldKey, e.target.value)}
disabled={!integrations[type.id]}
/>
</Box>
);
})}
<Box sx={{ mt: theme.spacing(1) }}>
<Button
variant="text"
color="info"
onClick={() => handleTestNotification(type.id)}
disabled={!integrations[type.id] || !areAllFieldsFilled()}
>
{t('notifications.testNotification')}
</Button>
</Box>
</>
);
};
export default TabComponent;
@@ -0,0 +1,46 @@
import React from "react";
import PropTypes from "prop-types";
import { Box } from "@mui/material";
import { useTheme } from "@emotion/react";
/**
* TabPanel component that displays content for the selected tab.
*
* @component
* @param {Object} props - The component props.
* @param {React.ReactNode} props.children - The content to be displayed when this tab panel is selected.
* @param {number} props.value - The currently selected tab value.
* @param {number} props.index - The index of this specific tab panel.
* @param {Object} props.other - Any additional props to be spread to the root element.
* @returns {React.ReactElement|null} The rendered tab panel or null if not selected.
*/
function TabPanel({ children, value, index, ...other }) {
const theme = useTheme();
return (
<div
role="tabpanel"
hidden={value !== index}
id={`notification-tabpanel-${index}`}
aria-labelledby={`notification-tab-${index}`}
{...other}
>
{value === index && (
<Box sx={{ pt: theme.spacing(3) }}>
{children}
</Box>
)}
</div>
);
}
TabPanel.propTypes = {
children: PropTypes.node,
index: PropTypes.number.isRequired,
value: PropTypes.number.isRequired
};
export default TabPanel;
+51
View File
@@ -0,0 +1,51 @@
.wallet-adapter-dropdown {
width: 100%;
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: var(--env-var-spacing-1);
}
.wallet-adapter-button {
display: flex;
align-items: center;
justify-content: center;
width: auto;
height: var(--env-var-height-2) !important;
font-size: var(--env-var-font-size-medium-plus) !important;
font-weight: 500 !important;
margin: 0;
padding: calc((var(--env-var-height-2) - var(--env-var-font-size-medium-plus) * 1.2) / 2) var(--env-var-spacing-1);
white-space: nowrap;
}
.wallet-adapter-modal-title {
font-size: var(--font-size-h5) !important;
}
/* Separator styling */
.wallet-adapter-modal-divider {
background-color: var(--border-color) !important;
margin: var(--spacing-md) 0 !important;
}
/* Responsive fixes */
@media (max-width: 1200px) {
.wallet-adapter-button {
font-size: var(--env-var-font-size-medium) !important;
padding: calc((var(--env-var-height-2) - var(--env-var-font-size-medium) * 1.2) / 2)
var(--env-var-spacing-1-minus) !important;
}
}
@media (max-width: 900px) {
.wallet-adapter-modal-wrapper {
flex-direction: column !important;
}
.wallet-adapter-modal-divider {
margin: var(--spacing-sm) 0 !important;
}
}
+1
View File
@@ -9,6 +9,7 @@ import {
import { WalletModalProvider } from "@solana/wallet-adapter-react-ui";
import { clusterApiUrl } from "@solana/web3.js";
import PropTypes from "prop-types";
import "./index.css";
// Default styles that can be overridden by your app
import "@solana/wallet-adapter-react-ui/styles.css";
+4 -1
View File
@@ -1,12 +1,15 @@
import React from 'react';
import AppBar from '../../Components/Common/AppBar';
import Footer from '../../Components/Common/Footer';
import { useTranslation } from "react-i18next";
const AboutUs = () => {
const { t } = useTranslation();
return (
<div>
<AppBar />
<h1>About Us</h1>
<h1>{t("aboutus")}</h1>
<Footer />
</div>
);
+11 -9
View File
@@ -5,6 +5,7 @@ import { useDispatch } from "react-redux";
import { useNavigate } from "react-router";
import { createToast } from "../../Utils/toastUtils";
import { forgotPassword } from "../../Features/Auth/authSlice";
import { useTranslation } from "react-i18next";
import Background from "../../assets/Images/background-grid.svg?react";
import EmailIcon from "../../assets/icons/email.svg?react";
import Logo from "../../assets/icons/checkmate-icon.svg?react";
@@ -15,6 +16,7 @@ const CheckEmail = () => {
const theme = useTheme();
const navigate = useNavigate();
const dispatch = useDispatch();
const { t } = useTranslation();
const [email, setEmail] = useState();
const [disabled, setDisabled] = useState(false);
@@ -161,9 +163,9 @@ const CheckEmail = () => {
<EmailIcon alt="email icon" />
</IconBox>
</Stack>
<Typography component="h1">Check your email</Typography>
<Typography component="h1">{t("authCheckEmailTitle")}</Typography>
<Typography>
We sent a password reset link to{" "}
{t("authCheckEmailDescription")}{" "}
<Typography
className="email-sent-to"
component="span"
@@ -181,22 +183,22 @@ const CheckEmail = () => {
maxWidth: 400,
}}
>
Open email app
{t("authCheckEmailOpenEmailButton")}
</Button>
<Typography sx={{ alignSelf: "center", mt: theme.spacing(6) }}>
Didn&apos;t receive the email?{" "}
{t("authCheckEmailDidntReceiveEmail")}{" "}
<Typography
component="span"
onClick={resendToken}
sx={{
color: theme.palette.primary.main,
color: theme.palette.accent.main,
userSelect: "none",
pointerEvents: disabled ? "none" : "auto",
cursor: disabled ? "default" : "pointer",
opacity: disabled ? 0.5 : 1,
}}
>
Click to resend
{t("authCheckEmailClickToResend")}
</Typography>
</Typography>
</Stack>
@@ -205,15 +207,15 @@ const CheckEmail = () => {
textAlign="center"
p={theme.spacing(12)}
>
<Typography display="inline-block">Go back to —</Typography>
<Typography display="inline-block">{t("goBackTo")}</Typography>
<Typography
component="span"
ml={theme.spacing(2)}
color={theme.palette.primary.main}
color={theme.palette.accent.main}
onClick={handleNavigate}
sx={{ userSelect: "none" }}
>
Log In
{t("authLoginTitle")}
</Typography>
</Box>
</Stack>
+8 -5
View File
@@ -11,6 +11,7 @@ import Logo from "../../assets/icons/checkmate-icon.svg?react";
import Key from "../../assets/icons/key.svg?react";
import Background from "../../assets/Images/background-grid.svg?react";
import IconBox from "../../Components/IconBox";
import { useTranslation } from "react-i18next";
import "./index.css";
const ForgotPassword = () => {
@@ -29,6 +30,8 @@ const ForgotPassword = () => {
email && setForm({ email: sessionStorage.getItem("email") });
}, []);
const { t } = useTranslation();
const handleSubmit = async (event) => {
event.preventDefault();
@@ -162,7 +165,7 @@ const ForgotPassword = () => {
<Key alt="password key icon" />
</IconBox>
</Stack>
<Typography component="h1">Forgot password?</Typography>
<Typography component="h1">{t("authForgotPasswordTitle")}</Typography>
<Typography>No worries, we&apos;ll send you reset instructions.</Typography>
</Box>
<Box
@@ -195,7 +198,7 @@ const ForgotPassword = () => {
mt: theme.spacing(15),
}}
>
Send instructions
{t("continue")}
</Button>
</Box>
</Stack>
@@ -204,15 +207,15 @@ const ForgotPassword = () => {
textAlign="center"
p={theme.spacing(12)}
>
<Typography display="inline-block">Go back to —</Typography>
<Typography display="inline-block">{t("goBackTo")}</Typography>
<Typography
component="span"
color={theme.palette.primary.main}
color={theme.palette.accent.main}
ml={theme.spacing(2)}
onClick={handleNavigate}
sx={{ userSelect: "none" }}
>
Log In
{t("authLoginTitle")}
</Typography>
</Box>
</Stack>
+8 -6
View File
@@ -8,12 +8,14 @@ import Background from "../../assets/Images/background-grid.svg?react";
import ConfirmIcon from "../../assets/icons/check-outlined.svg?react";
import Logo from "../../assets/icons/checkmate-icon.svg?react";
import IconBox from "../../Components/IconBox";
import { useTranslation } from "react-i18next";
import "./index.css";
const NewPasswordConfirmed = () => {
const theme = useTheme();
const navigate = useNavigate();
const dispatch = useDispatch();
const { t } = useTranslation();
const handleNavigate = () => {
dispatch(clearAuthState());
@@ -97,9 +99,9 @@ const NewPasswordConfirmed = () => {
<ConfirmIcon alt="password confirm icon" />
</IconBox>
</Stack>
<Typography component="h1">Password reset</Typography>
<Typography component="h1">{t("passwordreset")}</Typography>
<Typography mb={theme.spacing(2)}>
Your password has been successfully reset. Click below to log in magically.
{t("authNewPasswordConfirmed")}
</Typography>
</Box>
<Button
@@ -110,7 +112,7 @@ const NewPasswordConfirmed = () => {
maxWidth: 400,
}}
>
Continue
{t("continue")}
</Button>
</Stack>
</Stack>
@@ -118,15 +120,15 @@ const NewPasswordConfirmed = () => {
textAlign="center"
p={theme.spacing(12)}
>
<Typography display="inline-block">Go back to —</Typography>
<Typography display="inline-block">{t("goBackTo")}</Typography>
<Typography
component="span"
color={theme.palette.primary.main}
color={theme.palette.accent.main}
ml={theme.spacing(2)}
onClick={handleNavigate}
sx={{ userSelect: "none" }}
>
Log In
{t("authLoginTitle")}
</Typography>
</Box>
</Stack>
+2 -3
View File
@@ -35,7 +35,7 @@ const LandingPage = ({ isSuperAdmin, onSignup }) => {
textAlign="center"
>
<Box>
<Typography component="h1">Sign Up</Typography>
<Typography component="h1">{t("signUP")}</Typography>
<Typography>
{isSuperAdmin
? t("authRegisterCreateSuperAdminAccount")
@@ -67,7 +67,7 @@ const LandingPage = ({ isSuperAdmin, onSignup }) => {
</Box>
<Box maxWidth={400}>
<Typography className="tos-p">
{t("authRegisterBySigningUp")}
{t("authRegisterBySigningUp")}{" "}
<Typography
component="span"
onClick={() => {
@@ -239,7 +239,6 @@ const Register = ({ isSuperAdmin }) => {
const action = await dispatch(register(registerForm));
if (action.payload.success) {
const authToken = action.payload.data;
localStorage.setItem("token", authToken);
navigate("/uptime");
createToast({
body: "Welcome! Your account was created successfully.",
+6 -4
View File
@@ -4,6 +4,7 @@ import { useTheme } from "@emotion/react";
import { Box, Button, Stack, Typography } from "@mui/material";
import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
import TextInput from "../../../../Components/Inputs/TextInput";
import { useTranslation } from "react-i18next";
StepOne.propTypes = {
form: PropTypes.object,
@@ -28,6 +29,7 @@ StepOne.propTypes = {
function StepOne({ form, errors, onSubmit, onChange, onBack }) {
const theme = useTheme();
const inputRef = useRef(null);
const { t } = useTranslation();
useEffect(() => {
if (inputRef.current) {
@@ -43,8 +45,8 @@ function StepOne({ form, errors, onSubmit, onChange, onBack }) {
textAlign="center"
>
<Box>
<Typography component="h1">Sign Up</Typography>
<Typography>Enter your personal details</Typography>
<Typography component="h1">{t("signUp")}</Typography>
<Typography>{t("authRegisterStepOnePersonalDetails")}</Typography>
</Box>
<Box
@@ -107,7 +109,7 @@ function StepOne({ form, errors, onSubmit, onChange, onBack }) {
}}
>
<ArrowBackRoundedIcon />
Back
{t("commonBack")}
</Button>
<Button
variant="contained"
@@ -123,7 +125,7 @@ function StepOne({ form, errors, onSubmit, onChange, onBack }) {
},
}}
>
Continue
{t("continue")}
</Button>
</Stack>
</Box>
+12 -10
View File
@@ -19,6 +19,7 @@ import { monitorValidation } from "../../../Validation/validation";
import { useParams } from "react-router-dom";
import { useCreateDistributedUptimeMonitor } from "./Hooks/useCreateDistributedUptimeMonitor";
import { useMonitorFetch } from "./Hooks/useMonitorFetch";
import { useTranslation } from "react-i18next";
// Constants
const MS_PER_MINUTE = 60000;
@@ -41,6 +42,7 @@ const parseUrl = (url) => {
const CreateDistributedUptime = () => {
const { monitorId } = useParams();
const isCreate = typeof monitorId === "undefined";
const { t } = useTranslation();
let BREADCRUMBS = [
{ name: `distributed uptime`, path: "/distributed-uptime" },
@@ -191,14 +193,14 @@ const CreateDistributedUptime = () => {
fontSize="inherit"
fontWeight="inherit"
>
monitor
{t("monitor")}
</Typography>
</Typography>
<ConfigBox>
<Box>
<Typography component="h2">General settings</Typography>
<Typography component="h2">{t("settingsGeneralSettings")}</Typography>
<Typography component="p">
Here you can select the URL of the host, together with the type of monitor.
{t("distributedUptimeCreateSelectURL")}
</Typography>
</Box>
<Stack gap={theme.spacing(15)}>
@@ -232,9 +234,9 @@ const CreateDistributedUptime = () => {
</ConfigBox>
<ConfigBox>
<Box>
<Typography component="h2">Checks to perform</Typography>
<Typography component="h2">{t("distributedUptimeCreateChecks")}</Typography>
<Typography component="p">
You can always add or remove checks after adding your site.
{t("distributedUptimeCreateChecksDescription")}
</Typography>
</Box>
<Stack gap={theme.spacing(12)}>
@@ -259,14 +261,14 @@ const CreateDistributedUptime = () => {
filled={https.toString()}
onClick={() => setHttps(true)}
>
HTTPS
{t("https")}
</Button>
<Button
variant="group"
filled={(!https).toString()}
onClick={() => setHttps(false)}
>
HTTP
{t("http")}
</Button>
</ButtonGroup>
) : (
@@ -291,9 +293,9 @@ const CreateDistributedUptime = () => {
</ConfigBox>
<ConfigBox>
<Box>
<Typography component="h2">Incident notifications</Typography>
<Typography component="h2">{t("distributedUptimeCreateIncidentNotification")}</Typography>
<Typography component="p">
When there is an incident, notify users.
{t("distributedUptimeCreateIncidentDescription")}
</Typography>
</Box>
<Stack gap={theme.spacing(6)}>
@@ -310,7 +312,7 @@ const CreateDistributedUptime = () => {
</ConfigBox>
<ConfigBox>
<Box>
<Typography component="h2">Advanced settings</Typography>
<Typography component="h2">{t("distributedUptimeCreateAdvancedSettings")}</Typography>
</Box>
<Stack gap={theme.spacing(12)}>
<Select
@@ -5,6 +5,7 @@ import SmartToyIcon from "@mui/icons-material/SmartToy";
import Dot from "../../../../../Components/Dot";
// Utils
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
const MESSAGES = [
"I've checked the network status, and we're seeing excellent performance across all regions.",
@@ -16,6 +17,7 @@ const MESSAGES = [
const ChatBot = ({ sx }) => {
const theme = useTheme();
const { t } = useTranslation();
return (
<ColContainer
backgroundColor={theme.palette.chatbot.background}
@@ -37,7 +39,7 @@ const ChatBot = ({ sx }) => {
color={theme.palette.chatbot.textAccent}
sx={{ opacity: 0.4 }}
>
Now
{t("now")}
</Typography>
</Stack>
<Typography>{MESSAGES[Math.floor(Math.random() * MESSAGES.length)]}</Typography>
@@ -1,17 +1,18 @@
// Components
import { Box, Stack, Typography, Button } from "@mui/material";
import { Box, Stack, Button } from "@mui/material";
import Image from "../../../../../Components/Image";
import SettingsIcon from "../../../../../assets/icons/settings-bold.svg?react";
//Utils
import { useTheme } from "@mui/material/styles";
import { useNavigate } from "react-router-dom";
import { useLocation } from "react-router-dom";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
const Controls = ({ isDeleteOpen, setIsDeleteOpen, isDeleting, monitorId }) => {
const theme = useTheme();
const navigate = useNavigate();
const { t } = useTranslation();
return (
<Stack
@@ -25,7 +26,7 @@ const Controls = ({ isDeleteOpen, setIsDeleteOpen, isDeleting, monitorId }) => {
onClick={() => setIsDeleteOpen(!isDeleteOpen)}
loading={isDeleting}
>
Delete
{t("delete")}
</Button>
</Box>
<Box>
@@ -45,7 +46,7 @@ const Controls = ({ isDeleteOpen, setIsDeleteOpen, isDeleting, monitorId }) => {
},
}}
>
<SettingsIcon /> Configure
<SettingsIcon /> {t("configure")}
</Button>
</Box>
</Stack>
@@ -6,9 +6,11 @@ import { ColContainer } from "../../../../../Components/StandardContainer";
// Utils
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
const DeviceTicker = ({ data, width = "100%", connectionStatus }) => {
const theme = useTheme();
const { t } = useTranslation();
const statusColor = {
up: theme.palette.success.main,
down: theme.palette.error.main,
@@ -44,13 +46,13 @@ const DeviceTicker = ({ data, width = "100%", connectionStatus }) => {
<thead>
<tr>
<th style={{ textAlign: "left", paddingLeft: theme.spacing(4) }}>
<Typography>COUNTRY</Typography>
<Typography>{t("country")}</Typography>
</th>
<th style={{ textAlign: "left", paddingLeft: theme.spacing(4) }}>
<Typography>CITY</Typography>
<Typography>{t("city")}</Typography>
</th>
<th style={{ textAlign: "right", paddingLeft: theme.spacing(4) }}>
<Typography>RESPONSE</Typography>
<Typography>{t("response")}</Typography>
</th>
<th style={{ textAlign: "right", paddingLeft: theme.spacing(4) }}>
<Typography sx={{ whiteSpace: "nowrap" }}>{"UPT BURNED"}</Typography>
@@ -74,7 +76,7 @@ const DeviceTicker = ({ data, width = "100%", connectionStatus }) => {
<Typography>{city}</Typography>
</td>
<td style={{ textAlign: "right", paddingLeft: theme.spacing(4) }}>
<Typography>{Math.floor(dataPoint.responseTime)} ms</Typography>
<Typography>{Math.floor(dataPoint.responseTime)} {t("ms")}</Typography>
</td>
<td style={{ textAlign: "right", paddingLeft: theme.spacing(4) }}>
<Typography color={theme.palette.warning.main}>
@@ -4,9 +4,12 @@ import { formatDateWithTz } from "../../../../../../Utils/timeUtils";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import AccessTimeIcon from "@mui/icons-material/AccessTime";
import { useTranslation } from "react-i18next";
const CustomToolTip = ({ active, payload, label }) => {
const uiTimezone = useSelector((state) => state.ui.timezone);
const theme = useTheme();
const { t } = useTranslation();
if (active && payload && payload.length) {
const responseTime = payload[0]?.payload?.originalAvgResponseTime
? payload[0]?.payload?.originalAvgResponseTime
@@ -51,7 +54,7 @@ const CustomToolTip = ({ active, payload, label }) => {
component="span"
sx={{ opacity: 0.8 }}
>
Response time:
{t("responseTime")}
</Typography>
<Typography component="span">
{Math.floor(responseTime)}
@@ -59,7 +62,7 @@ const CustomToolTip = ({ active, payload, label }) => {
component="span"
sx={{ opacity: 0.8 }}
>
ms
{t("ms")}
</Typography>
</Typography>
</Stack>
@@ -8,8 +8,11 @@ import ResponseTimeIcon from "../../../../../assets/icons/response-time-icon.svg
// Utils
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
const DistributedUptimeResponseChart = ({ checks }) => {
const [chartType, setChartType] = useState("bar");
const { t } = useTranslation();
let Chart = null;
if (chartType === "area") {
Chart = DistributedUptimeResponseAreaChart;
@@ -23,13 +26,13 @@ const DistributedUptimeResponseChart = ({ checks }) => {
direction="row"
alignItems="center"
>
<Typography>Bar</Typography>
<Typography>{t("bar")}</Typography>
<Switch
color="main"
value={chartType}
onChange={(e) => setChartType(e.target.checked ? "area" : "bar")}
/>
<Typography>Area</Typography>
<Typography>{t("area")}</Typography>
</Stack>
<ChartBox
icon={<ResponseTimeIcon />}
@@ -1,15 +1,18 @@
import { Stack, Typography, Box } from "@mui/material";
import SolanaLogo from "../../../../../assets/icons/solana_logo.svg?react";
import { useTheme } from "@mui/material/styles";
import { useTranslation } from "react-i18next";
const Footer = () => {
const theme = useTheme();
const { t } = useTranslation();
return (
<Stack
justifyContent="space-between"
alignItems="center"
spacing={2}
>
<Typography variant="h2">Made with ❤️ by UpRock & Bluewave Labs</Typography>
<Typography variant="h2">{t("distributedUptimeDetailsFooterHeading")}</Typography>
<Stack
width="100%"
direction="row"
@@ -17,12 +20,12 @@ const Footer = () => {
justifyContent="center"
alignItems="center"
>
<Typography variant="h2">Built on</Typography>
<Typography variant="h2">{t("distributedUptimeDetailsFooterBuilt")}</Typography>
<SolanaLogo
width={15}
height={15}
/>
<Typography variant="h2">Solana</Typography>
<Typography variant="h2">{t("distributedUptimeDetailsFooterSolana")}</Typography>
</Stack>
</Stack>
);
@@ -1,15 +1,17 @@
import { Stack, Typography } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
const MonitorHeader = ({ monitor }) => {
const theme = useTheme();
const { t } = useTranslation();
return (
<Stack direction="row">
<Stack gap={theme.spacing(2)}>
<Typography variant="h1">{monitor.name}</Typography>
<Typography variant="h2">
Distributed Uptime Monitoring powered by DePIN
{t("distributedUptimeDetailsMonitorHeader")}
</Typography>
</Stack>
</Stack>
@@ -9,11 +9,6 @@ import PropTypes from "prop-types";
const StatBoxes = ({ monitor, lastUpdateTrigger }) => {
const theme = useTheme();
let timeSinceLastCheck = 0;
if (monitor?.lastCheckTimestamp) {
timeSinceLastCheck = Date.now() - monitor?.lastCheckTimestamp;
}
return (
<Stack
@@ -37,7 +32,7 @@ const StatBoxes = ({ monitor, lastUpdateTrigger }) => {
heading={"Last check"}
subHeading={
<LastUpdate
lastUpdateTime={timeSinceLastCheck}
lastUpdateTime={monitor?.timeSinceLastCheck}
suffix={"seconds ago"}
/>
}
@@ -9,9 +9,11 @@ import ShareComponent from "../../../../../Components/ShareComponent";
// Utils
import { useTheme } from "@emotion/react";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
const StatusHeader = ({ monitor, connectionStatus, elementToCapture }) => {
const theme = useTheme();
const { t } = useTranslation();
const COLOR_MAP = {
up: theme.palette.successSecondary.main,
down: theme.palette.error.lowContrast,
@@ -28,7 +30,6 @@ const StatusHeader = ({ monitor, connectionStatus, elementToCapture }) => {
};
let bgColor = COLOR_MAP[connectionStatus];
return (
<ColContainer backgroundColor={bgColor}>
<Stack
@@ -59,17 +60,18 @@ const StatusHeader = ({ monitor, connectionStatus, elementToCapture }) => {
backgroundColor={theme.palette.successSecondary.lowContrast}
color={theme.palette.success.lowContrast}
>
Uptime: {(monitor.totalUptime * 100).toFixed(2)}%
{t("distributedUptimeDetailsStatusHeaderUptime")}{" "}
{(monitor.uptimePercentage * 100).toFixed(2)}%
</Typography>
</Stack>
<Typography
variant="body2"
color={theme.palette.success.lowContrast}
>
Last updated{" "}
{t("distributedUptimeDetailsStatusHeaderLastUpdate")}{" "}
<LastUpdate
suffix={"seconds ago"}
lastUpdateTime={monitor.timeSinceLastCheck}
lastUpdateTime={monitor?.timeSinceLastCheck}
/>
</Typography>
</Stack>
@@ -22,6 +22,7 @@ import { useIsAdmin } from "../../../Hooks/useIsAdmin";
import { useSubscribeToDetails } from "./Hooks/useSubscribeToDetails";
import { useDeleteMonitor } from "./Hooks/useDeleteMonitor";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
const DistributedUptimeDetails = () => {
const { monitorId } = useParams();
@@ -32,6 +33,7 @@ const DistributedUptimeDetails = () => {
// Utils
const theme = useTheme();
const isAdmin = useIsAdmin();
const { t } = useTranslation();
const navigate = useNavigate();
const [isLoading, networkError, connectionStatus, monitor, lastUpdateTrigger] =
useSubscribeToDetails({ monitorId, dateRange });
@@ -55,9 +57,9 @@ const DistributedUptimeDetails = () => {
marginY={theme.spacing(4)}
color={theme.palette.primary.contrastTextTertiary}
>
Network error
{t("networkError")}
</Typography>
<Typography>Please check your connection</Typography>
<Typography>{t("checkConnection")}</Typography>
</GenericFallback>
);
}
@@ -70,7 +72,7 @@ const DistributedUptimeDetails = () => {
<Stack gap={theme.spacing(10)}>
<Breadcrumbs list={BREADCRUMBS} />
<GenericFallback>
<Typography>There is no check history for this monitor yet.</Typography>
<Typography>{t("distributedUptimeDetailsNoMonitorHistory")}</Typography>
</GenericFallback>
</Stack>
);
@@ -13,6 +13,7 @@ import { useTheme } from "@mui/material/styles";
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
import { useSubscribeToMonitors } from "./Hooks/useSubscribeToMonitors";
import SkeletonLayout from "./Components/Skeleton";
import { useTranslation } from "react-i18next";
// Constants
const BREADCRUMBS = [{ name: `Distributed Uptime`, path: "/distributed-uptime" }];
@@ -24,6 +25,7 @@ const DistributedUptimeMonitors = () => {
// Utils
const theme = useTheme();
const isAdmin = useIsAdmin();
const { t } = useTranslation();
const [isLoading, networkError, monitors, monitorsSummary, filteredMonitors] =
useSubscribeToMonitors(page, rowsPerPage);
// Handlers
@@ -48,9 +50,9 @@ const DistributedUptimeMonitors = () => {
marginY={theme.spacing(4)}
color={theme.palette.primary.contrastTextTertiary}
>
Network error
{t("networkError")}
</Typography>
<Typography>Please check your connection</Typography>
<Typography>{t("checkConnection")}</Typography>
</GenericFallback>
);
}
+2 -1
View File
@@ -298,7 +298,7 @@ const Settings = () => {
{t("settingsWalletDescription")}
</Typography>
</Box>
<Box>
<Box sx={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'flex-start', gap: 2 }}>
<Stack
direction="row"
spacing={2}
@@ -401,6 +401,7 @@ const Settings = () => {
</ConfigBox>
)}
<ConfigBox>
<Box>
<Typography component="h1">{t("settingsAbout")}</Typography>
-2
View File
@@ -24,7 +24,6 @@ const PublicStatus = () => {
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
// Utils
const theme = useTheme();
const isAdmin = useIsAdmin();
const location = useLocation();
const navigate = useNavigate();
@@ -33,7 +32,6 @@ const PublicStatus = () => {
const [deleteStatusPage, isDeleting] = useStatusPageDelete(fetchStatusPage, url);
// Setup
const currentPath = location.pathname;
let sx = { paddingLeft: theme.spacing(20), paddingRight: theme.spacing(20) };
let link = undefined;
const isPublic = location.pathname.startsWith("/status/uptime/public");
@@ -1,7 +1,7 @@
import DataTable from "../../../../../Components/Table";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
import { ColoredLabel } from "../../../../../Components/Label";
import { StatusLabel } from "../../../../../Components/Label";
import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward";
import { Stack, Typography } from "@mui/material";
const StatusPagesTable = ({ data }) => {
@@ -25,7 +25,7 @@ const StatusPagesTable = ({ data }) => {
row.type === "distributed"
? `/status/distributed/public/${row.url}`
: `/status/uptime/public/${row.url}`;
navigate(url);
window.open(url, "_blank", "noopener,noreferrer")
}
},
render: (row) => {
@@ -66,12 +66,11 @@ const StatusPagesTable = ({ data }) => {
id: "status",
content: "Status",
render: (row) => {
const status = row.isPublished ? "published" : "unpublished";
return (
<ColoredLabel
label={row.isPublished ? "Published" : "Unpublished"}
color={
row.isPublished ? theme.palette.success.main : theme.palette.warning.main
}
<StatusLabel
status={status}
text={row.isPublished ? "Published" : "Unpublished"}
/>
);
},
+16 -3
View File
@@ -23,6 +23,7 @@ import Radio from "../../../Components/Inputs/Radio";
import Checkbox from "../../../Components/Inputs/Checkbox";
import Select from "../../../Components/Inputs/Select";
import ConfigBox from "../../../Components/ConfigBox";
import NotificationIntegrationModal from "../../../Components/NotificationIntegrationModal/NotificationIntegrationModal";
const CreateMonitor = () => {
const MS_PER_MINUTE = 60000;
const SELECT_VALUES = [
@@ -80,6 +81,11 @@ const CreateMonitor = () => {
];
// State
const [isNotificationModalOpen, setIsNotificationModalOpen] = useState(false);
const handleOpenNotificationModal = () => {
setIsNotificationModalOpen(true);
};
const [errors, setErrors] = useState({});
const [https, setHttps] = useState(true);
const [monitor, setMonitor] = useState({
@@ -407,15 +413,15 @@ const CreateMonitor = () => {
onChange={(event) => handleNotifications(event, "email")}
/>
<Box mt={theme.spacing(2)}>
{/* <Box mt={theme.spacing(2)}>
<Button
variant="contained"
color="accent"
onClick={handleAddNotification}
onClick={handleOpenNotificationModal}
>
Notification Integration
</Button>
</Box>
</Box> */}
</Stack>
</ConfigBox>
@@ -512,6 +518,13 @@ const CreateMonitor = () => {
</Button>
</Stack>
</Stack>
<NotificationIntegrationModal
open={isNotificationModalOpen}
onClose={() => setIsNotificationModalOpen(false)}
monitor={monitor}
setMonitor={setMonitor}
/>
</Box>
);
};
@@ -2,18 +2,30 @@ import { useState } from "react";
import { useEffect } from "react";
import { networkService } from "../../../../main";
import { createToast } from "../../../../Utils/toastUtils";
export const useChecksFetch = ({ monitorId, dateRange, page, rowsPerPage }) => {
export const useChecksFetch = ({
monitorId,
monitorType,
dateRange,
page,
rowsPerPage,
}) => {
const [checks, setChecks] = useState(undefined);
const [checksCount, setChecksCount] = useState(undefined);
const [isLoading, setIsLoading] = useState(false);
const [networkError, setNetworkError] = useState(false);
useEffect(() => {
if (!monitorType) {
console.warn("Monitor Type is not provided. Fetching checks will not proceed.");
return;
}
const fetchChecks = async () => {
try {
setIsLoading(true);
const res = await networkService.getChecksByMonitor({
monitorId: monitorId,
type: monitorType,
sortOrder: "desc",
limit: null,
dateRange: dateRange,
@@ -31,7 +43,7 @@ export const useChecksFetch = ({ monitorId, dateRange, page, rowsPerPage }) => {
}
};
fetchChecks();
}, [monitorId, dateRange, page, rowsPerPage]);
}, [monitorId, monitorType, dateRange, page, rowsPerPage]);
return [checks, checksCount, isLoading, networkError];
};
+3
View File
@@ -59,8 +59,11 @@ const UptimeDetails = () => {
uiTimezone,
});
const monitorType = monitor?.type;
const [checks, checksCount, checksAreLoading, checksNetworkError] = useChecksFetch({
monitorId,
monitorType,
dateRange,
page,
rowsPerPage,
+213 -35
View File
@@ -117,6 +117,33 @@ const baseTheme = (palette) => ({
color: `${theme.palette.secondary.contrastText} !important`,
},
},
{
props: { variant: 'text', color: 'info' },
style: {
textDecoration: 'underline',
color: theme.palette.text.primary,
padding: 0,
margin: 0,
fontSize: typographyLevels.m,
fontWeight: theme.typography.body2.fontWeight,
backgroundColor: 'transparent',
'&:hover': {
backgroundColor: 'transparent',
textDecoration: 'underline'
},
"&.Mui-disabled": {
backgroundColor: theme.palette.secondary.main,
color: theme.palette.primary.contrastText,
"&.MuiButton-text": {
backgroundColor: 'transparent'
}
},
minWidth: 0,
boxShadow: 'none',
border: 'none'
},
},
],
height: 34,
fontWeight: 400,
@@ -305,13 +332,13 @@ const baseTheme = (palette) => ({
color: palette.error.main,
opacity: 0.8,
fontSize: "var(--env-var-font-size-medium)",
marginLeft: 0,
},
"& .MuiFormHelperText-root.Mui-error": {
opacity: 0.8,
fontSize: "var(--env-var-font-size-medium)",
color: palette.error.main,
whiteSpace: 'nowrap',
},
}),
},
@@ -328,35 +355,68 @@ const baseTheme = (palette) => ({
},
MuiTab: {
styleOverrides: {
root: ({ theme }) => ({
fontSize: 13,
color: theme.palette.tertiary.contrastText,
backgroundColor: theme.palette.tertiary.main,
textTransform: "none",
minWidth: "fit-content",
paddingY: theme.spacing(6),
fontWeight: 400,
borderBottom: "2px solid transparent",
borderRight: `1px solid ${theme.palette.primary.lowContrast}`,
"&:first-of-type": { borderTopLeftRadius: "8px" },
"&:last-child": { borderTopRightRadius: "8px", borderRight: 0 },
"&:focus-visible": {
color: theme.palette.primary.contrastText,
borderColor: theme.palette.tertiary.contrastText,
borderRightColor: theme.palette.primary.lowContrast,
},
"&.Mui-selected": {
backgroundColor: theme.palette.secondary.main,
color: theme.palette.secondary.contrastText,
borderColor: theme.palette.secondary.contrastText,
borderRightColor: theme.palette.primary.lowContrast,
},
"&:hover": {
borderColor: theme.palette.primary.lowContrast,
},
}),
root: ({ theme }) => ({
fontSize: theme.typography.fontSize - 1,
color: theme.palette.tertiary.contrastText,
backgroundColor: theme.palette.tertiary.main,
textTransform: "none",
minWidth: "fit-content",
padding: `${theme.spacing(6)}px ${theme.spacing(4)}px`,
fontWeight: 400,
borderBottom: `${theme.shape.borderThick}px solid transparent`,
borderRight: `${theme.shape.borderRadius / 2}px solid ${theme.palette.primary.lowContrast}`,
"&:first-of-type": { borderTopLeftRadius: theme.shape.borderRadius * 4 },
"&:last-child": { borderTopRightRadius: theme.shape.borderRadius * 4, borderRight: 0 },
"&:focus-visible": {
color: theme.palette.primary.contrastText,
borderColor: theme.palette.tertiary.contrastText,
borderRightColor: theme.palette.primary.lowContrast,
},
"&.Mui-selected": {
backgroundColor: theme.palette.secondary.main,
color: theme.palette.secondary.contrastText,
borderColor: theme.palette.secondary.contrastText,
borderRightColor: theme.palette.primary.lowContrast,
},
"&:hover": {
borderColor: theme.palette.primary.lowContrast,
},
}),
},
},
variants: [
{
props: { orientation: 'vertical' },
style: ({ theme }) => ({
alignItems: 'flex-start',
padding: `${theme.spacing(1)}px ${theme.spacing(2)}px ${theme.spacing(1)}px ${theme.spacing(6)}px`,
minHeight: theme.spacing(12),
color: theme.palette.primary.contrastText,
backgroundColor: theme.palette.primary.main,
border: 'none',
borderBottom: 'none',
borderRight: 'none',
borderRadius: theme.shape.borderRadius * 3,
margin: `${theme.spacing(1)}px ${theme.spacing(2)}px`,
'&.Mui-selected': {
color: theme.palette.primary.contrastText,
backgroundColor: theme.palette.tertiary.main,
opacity: 1,
border: 'none',
borderBottom: 'none',
borderRight: 'none',
borderRadius: theme.shape.borderRadius * 3,
minHeight: theme.spacing(14)
},
'&:hover': {
backgroundColor: theme.palette.tertiary.main,
border: 'none',
borderRadius: theme.shape.borderRadius * 3,
minHeight: theme.spacing(14)
}
}),
},
],
},
MuiSvgIcon: {
styleOverrides: {
root: ({ theme }) => ({
@@ -366,13 +426,23 @@ const baseTheme = (palette) => ({
},
MuiTabs: {
styleOverrides: {
root: ({ theme }) => ({
"& .MuiTabs-indicator": {
backgroundColor: theme.palette.tertiary.contrastText,
},
}),
root: ({ theme }) => ({
"& .MuiTabs-indicator": {
backgroundColor: theme.palette.tertiary.contrastText,
},
}),
},
},
variants: [
{
props: { orientation: 'vertical' },
style: {
"& .MuiTabs-indicator": {
display: 'none',
}
},
},
],
},
MuiSwitch: {
styleOverrides: {
root: ({ theme }) => ({
@@ -394,6 +464,114 @@ const baseTheme = (palette) => ({
}),
},
},
// This code is added for clock in maintenance page
// code starts from here.
MuiClock: {
// Directly target the clock component
styleOverrides: {
root: ({ theme }) => ({
backgroundColor: theme.palette.primary.main, // Alternative target
"& .MuiClock-clock": {
// Inner clock face
backgroundColor: theme.palette.secondary.main,
},
}),
},
},
MuiClockPicker: {
styleOverrides: {
root: ({ theme }) => ({
backgroundColor: theme.palette.secondary.main, // Outer container background
"& .MuiClock-root": {
color: theme.palette.primary.lowContrast,
},
"& .MuiClock-clock": {
backgroundColor: theme.palette.background.default, // Clock face background
borderColor: theme.palette.secondary.lowContrast,
},
}),
},
},
// The clock pointer ( pointer to number like hour/minute hand)
MuiClockPointer: {
styleOverrides: {
root: ({ theme }) => ({
// Main pointer line color
backgroundColor: theme.palette.accent.main,
"& .MuiClockPointer-thumb": {
backgroundColor: theme.palette.grey[500], // Use your desired grey
},
}),
},
},
// This is for numbers in the clock (circular one's)
MuiClockNumber: {
styleOverrides: {
root: ({ theme }) => ({
color: theme.palette.primary.contrastText,
"&.Mui-selected": {
color: theme.palette.accent.contrastText,
backgroundColor: theme.palette.accent.main,
},
}),
},
},
// This is for 00:00 am and pm (top bar)
MuiTimePickerToolbar: {
styleOverrides: {
root: ({ theme }) => ({
backgroundColor: theme.palette.secondary.lowContrast,
// General text color
"& .MuiTypography-root": {
color: theme.palette.primary.contrastTextTertiary,
},
// Selected time (hour/minute) color
"& .Mui-selected": {
color: `${theme.palette.accent.main} !important`,
},
// AM/PM buttons color
"& .MuiButtonBase-root": {
"&.Mui-selected": {
backgroundColor: theme.palette.accent.main,
},
},
}),
},
},
// left and right direction style
MuiPickersArrowSwitcher: {
styleOverrides: {
root: ({ theme }) => ({
"& .MuiIconButton-root": {
color: theme.palette.primary.contrastText,
},
}),
},
},
// cancel and okay actions style
MuiDialogActions: {
styleOverrides: {
root: ({ theme }) => ({
backgroundColor: theme.palette.primary.main,
"& .MuiButton-root": {
color: theme.palette.primary.contrastText,
},
}),
},
},
// code ends here.
// For labels of input fields
MuiInputLabel: {
styleOverrides: {
root: ({ theme }) => ({
"&.Mui-focused": {
color: theme.palette.accent.main,
},
}),
},
},
},
shape: {
borderRadius: 2,
+67 -2
View File
@@ -1,12 +1,29 @@
{
"dontHaveAccount": "Don't have account",
"https": "HTTPS",
"http": "HTTP",
"monitor": "monitor",
"aboutus": "About Us",
"email": "E-mail",
"forgotPassword": "Forgot Password",
"password": "password",
"signUp": "Sign up",
"signUP": "Sign Up",
"submit": "Submit",
"title": "Title",
"continue": "Continue",
"now": "Now",
"delete": "Delete",
"configure": "Configure",
"networkError": "Network error",
"responseTime": "Response time:",
"ms": "ms",
"bar": "Bar",
"area": "Area",
"country": "COUNTRY",
"city": "CITY",
"response": "RESPONSE",
"checkConnection": "Please check your connection",
"enterEmail": "Enter your email",
"authLoginTitle": "Log In",
"authLoginEnterPassword": "Enter your password",
@@ -16,11 +33,13 @@
"commonBack": "Back",
"authForgotPasswordTitle": "Forgot password?",
"authForgotPasswordResetPassword": "Reset password",
"passwordreset": "Password Reset",
"authRegisterAlreadyHaveAccount": "Already have an account?",
"commonAppName": "Checkmate",
"authLoginEnterEmail": "Enter your email",
"authRegisterTitle": "Create an account",
"authRegisterStepOneTitle": "Create your account",
"authRegisterStepOnePersonalDetails": "Enter your personal details",
"authRegisterStepOneDescription": "Enter your details to get started",
"authRegisterStepTwoTitle": "Set up your profile",
"authRegisterStepTwoDescription": "Tell us more about yourself",
@@ -30,9 +49,10 @@
"authForgotPasswordSendInstructions": "Send instructions",
"authForgotPasswordBackTo": "Back to",
"authCheckEmailTitle": "Check your email",
"authCheckEmailDescription": "We sent a password reset link to {{email}}",
"authCheckEmailDescription": "We sent a password reset link to",
"authCheckEmailResendEmail": "Resend email",
"authCheckEmailBackTo": "Back to",
"authCheckEmailOpenEmailButton": "Open email app",
"goBackTo": "Go back to",
"authCheckEmailDidntReceiveEmail": "Didn't receive the email?",
"authCheckEmailClickToResend": "Click to resend",
@@ -42,6 +62,7 @@
"authSetNewPasswordConfirmPassword": "Confirm password",
"confirmPassword": "Confirm your password",
"authSetNewPasswordResetPassword": "Reset password",
"authNewPasswordConfirmed": "Your password has been successfully reset. Click below to log in magically.",
"authSetNewPasswordBackTo": "Back to",
"authPasswordMustBeAtLeast": "Must be at least",
"authPasswordCharactersLong": "8 characters long",
@@ -66,6 +87,19 @@
"distributedRightCategoryTitle": "Monitor",
"distributedStatusServerMonitors": "Server Monitors",
"distributedStatusServerMonitorsDescription": "Monitor status of related servers",
"distributedUptimeCreateSelectURL": "Here you can select the URL of the host, together with the type of monitor.",
"distributedUptimeCreateChecks": "Checks to perform",
"distributedUptimeCreateChecksDescription": "You can always add or remove checks after adding your site.",
"distributedUptimeCreateIncidentNotification": "Incident notifications",
"distributedUptimeCreateIncidentDescription": "When there is an incident, notify users.",
"distributedUptimeCreateAdvancedSettings": "Advanced settings",
"distributedUptimeDetailsNoMonitorHistory": "There is no check history for this monitor yet.",
"distributedUptimeDetailsFooterHeading": "Made with ❤️ by UpRock & Bluewave Labs",
"distributedUptimeDetailsFooterBuilt": "Built on",
"distributedUptimeDetailsFooterSolana": "Solana",
"distributedUptimeDetailsMonitorHeader": "Distributed Uptime Monitoring powered by DePIN",
"distributedUptimeDetailsStatusHeaderUptime": "Uptime:",
"distributedUptimeDetailsStatusHeaderLastUpdate": "Last updated",
"settingsGeneralSettings": "General settings",
"settingsDisplayTimezone": "Display timezone",
@@ -110,5 +144,36 @@
"settingsFailedToDeleteMonitors": "Failed to delete all monitors",
"starPromptTitle": "Star Checkmate",
"starPromptDescription": "See the latest releases and help grow the community on GitHub"
"notifications": {
"enableNotifications": "Enable {{platform}} notifications",
"testNotification": "Test notification",
"addOrEditNotifications": "Add or edit notifications",
"slack": {
"label": "Slack",
"description": "To enable Slack notifications, create a Slack app and enable incoming webhooks. After that, simply provide the webhook URL here.",
"webhookLabel": "Webhook URL",
"webhookPlaceholder": "https://hooks.slack.com/services/..."
},
"discord": {
"label": "Discord",
"description": "To send data to a Discord channel from Checkmate via Discord notifications using webhooks, you can use Discord's incoming Webhooks feature.",
"webhookLabel": "Discord Webhook URL",
"webhookPlaceholder": "https://discord.com/api/webhooks/..."
},
"telegram": {
"label": "Telegram",
"description": "To enable Telegram notifications, create a Telegram bot using BotFather, an official bot for creating and managing Telegram bots. Then, get the API token and chat ID and write them down here.",
"tokenLabel": "Your bot token",
"tokenPlaceholder": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
"chatIdLabel": "Your Chat ID",
"chatIdPlaceholder": "-1001234567890"
},
"webhook": {
"label": "Webhooks",
"description": "You can set up a custom webhook to receive notifications when incidents occur.",
"urlLabel": "Webhook URL",
"urlPlaceholder": "https://your-server.com/webhook"
}
}
}