feat: add dark mode (#409)

* feat: add dark mode. Issue #348

* feat: add darkmode and fix btn for the landing page and login. Issue #348

* test: update test cases. Issue #348

* chore: fix sonarcloud code smell. Issue #348
This commit is contained in:
2025-04-16 13:17:36 -03:00
committed by GitHub
parent 8fd9c05933
commit 1e906a0f01
27 changed files with 436 additions and 321 deletions
+25 -2
View File
@@ -1,4 +1,4 @@
import React, { useContext, useEffect } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import {
createBrowserRouter,
Navigate,
@@ -24,6 +24,9 @@ import './styles/custom.scss';
*/
function App(): React.ReactNode {
const { signed, checkCurrentAuthUser } = useContext(AuthContext);
const [theme, setTheme] = useState(() => {
return localStorage.getItem('theme') ?? 'light';
});
/**
* Routes for the users who are not signed in.
@@ -90,8 +93,28 @@ function App(): React.ReactNode {
checkCurrentAuthUser(window.location.pathname);
}, []);
useEffect(() => {
document.body.setAttribute('data-bs-theme', theme);
localStorage.setItem('theme', theme);
}, [theme]);
const toggleTheme = () => {
setTheme(prev => (prev === 'light' ? 'dark' : 'light'));
};
return (
<RouterProvider router={browserRouter} />
<>
<button
type="button"
className="btn btn-dark"
onClick={toggleTheme}
>
{theme === 'dark' ? '☀ Light Mode' : '🌙 Dark Mode'}
</button>
{/* Your routes or content here */}
<RouterProvider router={browserRouter} />
</>
);
};
@@ -41,7 +41,7 @@ describe('ContentHeader test cases', () => {
expect(getByText(headerProps.h2GreenText)).toBeDefined();
// renders only if it's the home (dashboard)
expect(getByText("Add note")).toBeDefined();
expect(getByText("Add task")).toBeDefined();
expect(getByText("New note")).toBeDefined();
expect(getByText("New task")).toBeDefined();
});
});
+1 -1
View File
@@ -61,7 +61,7 @@ describe('Account Component', () => {
it('should render the Account component', () => {
const { getByText } = renderAccount();
expect(getByText('Change your info')).toBeDefined();
expect(getByText('Update only what you need. Blank fields will not be updated')).toBeDefined();
});
it('should change language when a language button is clicked', () => {
Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

@@ -5,7 +5,7 @@ import { TasksChartResponse } from '../../types/TasksChartResponse';
import api from '../../api-service/api';
import ApiConfig from '../../api-service/apiConfig';
import ChartIcon from '../../assets/chart-icon.svg';
import './style.css';
import './style.scss';
type Series = {
label: string;
@@ -1,5 +1,7 @@
@import '../../styles/theme.scss';
.completed-tasks {
background-color: #fff;
background-color: var(--bs-body-bg);
padding: 1rem;
border-radius: 4px;
@@ -1,6 +1,5 @@
import React, { useContext } from 'react';
import { Col, Row } from 'react-bootstrap';
import { PlusCircleFill } from 'react-bootstrap-icons';
import { NavLink } from 'react-router';
import SidebarContext from '../../context/SidebarContext';
@@ -55,8 +54,7 @@ const ContentHeader: React.FC<Props> = (props: Props): React.ReactNode => {
type="button"
className="home-new-item w-45 mb-2"
>
<PlusCircleFill size={25} />
Add task
New task
</button>
</NavLink>
<NavLink to="/notes/new" onClick={() => setNewPage('/notes/new')}>
@@ -64,8 +62,7 @@ const ContentHeader: React.FC<Props> = (props: Props): React.ReactNode => {
type="button"
className="home-new-item w-45 ms-2"
>
<PlusCircleFill size={25} />
Add note
New note
</button>
</NavLink>
</Col>
@@ -1,9 +1,13 @@
/* custom-datepicker.css */
@import '../../styles/theme.scss';
/* custom-datepicker.scss */
.react-datepicker__input-container input {
font-size: 16px; /* Prevents iOS zoom on focus */
width: 100%;
border-radius: 4px;
border: 1px solid #ced4da;
border: 1px solid var(--bs-border-color);
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.react-datepicker-popper {
+2 -2
View File
@@ -2,10 +2,10 @@ import React, { useEffect, useState } from 'react';
import { Col, Form, InputGroup, Row } from 'react-bootstrap';
import * as Icons from 'react-bootstrap-icons';
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';
import './custom-datepicker.css';
import { MiddlewareReturn } from '@floating-ui/core';
import { MiddlewareState } from '@floating-ui/dom';
import 'react-datepicker/dist/react-datepicker.css';
import './custom-datepicker.scss';
type IconName = keyof typeof Icons;
@@ -44,7 +44,7 @@ const ModalMarkdown: React.FC<Props> = (props: Props): React.ReactNode => {
<Markdown>{props.markdownText}</Markdown>
</Modal.Body>
<Modal.Footer>
<Button variant="primary" onClick={props.onHide}>
<Button variant="outline-secondary" onClick={props.onHide}>
Close
</Button>
</Modal.Footer>
+3 -6
View File
@@ -43,10 +43,10 @@ function Sidebar(props: React.PropsWithChildren<Props>): React.ReactNode {
<i className="bi bi-list"></i>
</button>
<div className={`d-flex flex-column vh-100 bg-light sidebar ${props.isMobileOpen ? 'sidebar-mobile-open' : ''}`}>
<div className={`d-flex flex-column vh-100 sidebar ${props.isMobileOpen ? 'sidebar-mobile-open' : ''}`}>
<div className="sidebar-header plus-jakarta-sans-bold">
<img src={`https://gravatar.com/avatar/${user?.gravatarImageUrl}.jpg`} alt="User icon" />
<span>{user?.name ? user?.name : 'User'}</span>
<span className="header-username">{user?.name ? user?.name : 'User'}</span>
</div>
<div className="header-spacer"></div>
@@ -115,10 +115,7 @@ function Sidebar(props: React.PropsWithChildren<Props>): React.ReactNode {
</Nav>
{/* Footer at the bottom */}
<div
className="mt-auto text-center text-muted py-3"
style={{ borderTop: '1px solid #ddd' }}
>
<div className="mt-auto text-center text-muted py-3">
<small data-testid="footer-text">
{build}
</small>
+6 -2
View File
@@ -2,7 +2,7 @@
.sidebar {
width: 276px;
background-color: #fff;
background-color: var(--bs-body-bg);
position: fixed;
left: 0;
top: 0;
@@ -40,10 +40,14 @@
}
}
.header-username {
color: var(--bs-body-color);
}
.header-spacer {
margin-top: 1.8rem;
margin-left: 1rem;
border-bottom: 0.5px dashed #DCDCDC;
border-bottom: 0.5px dashed var(--bs-header-sidebar-color);
width: 220px;
}
.sidebar-header img {
+44 -18
View File
@@ -1,20 +1,18 @@
@import './theme.scss';
@import '~bootstrap/scss/bootstrap';
@import './theme.scss';
$light-bg: #E6F5E9;
$dark-text: #343a40;
body {
background-color: #E6F5E9;
background-color: var(--bs-custom-body-bg);
}
a, .btn-link {
color: map-get($theme-colors, 'primary');
color: var(--bs-primary);
text-decoration: none;
}
a:hover, .btn-link:hover {
color: map-get($theme-colors, 'primary');
color: var(--bs-primary);
}
.poppins-thin {
@@ -194,7 +192,7 @@ a:hover, .btn-link:hover {
}
.home-productive {
color: map-get($theme-colors, 'primary');
color: var(--bs-primary);
}
.font-size-14 {
@@ -210,16 +208,28 @@ a:hover, .btn-link:hover {
}
.home-new-item {
border: none;
border-radius: 4px;
border: 1px solid var(--bs-btn-new-btn-item-bg);
border-radius: 3px;
padding: 8px 16px;
color: #fff;
background-color: #233C46;
color: var(--bs-btn-new-btn-item-color);
background-color: var(--bs-btn-new-btn-item-bg);
font-weight: 500;
}
.home-new-item svg {
margin-right: 1rem;
color: #4CD964;
.home-new-item:hover {
background-color: #36d451;
}
.home-new-item-secondary {
border: 1px solid #272d33;
border-radius: 3px;
padding: 8px 16px;
color: #e0e0e0;
background-color: #272d33;
}
.home-new-item-secondary:hover {
background-color: #333b42;
}
.task-note-btn {
@@ -227,17 +237,17 @@ a:hover, .btn-link:hover {
}
pre {
background-color: #f5f5f5;
border: 1px solid #ddd;
background-color: var(--bs-body-bg);
border: 1px solid var(--bs-border-color);
border-radius: 4px;
padding: 10px;
margin-top: 5px;
overflow-x: auto; /* Enables horizontal scrolling for long lines */
overflow-x: auto;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 12px;
line-height: 1.5;
tab-size: 2; /* Controls tab width */
white-space: pre; /* Preserves whitespace */
white-space: pre;
}
code {
@@ -253,3 +263,19 @@ code {
border-radius: 3px;
color: #d63384;
}
.input-group-text {
border-right: none !important;
padding: 0.375rem 0.05rem 0.375rem 0.75rem;
}
.input-group > .form-control,
.react-datepicker__input-container > .form-control {
border-left: none !important;
}
.dropdown-toggle.btn.btn-success {
background-color: var(--bs-body-bg);
color: var(--bs-body-color);
border: none;
}
+28
View File
@@ -0,0 +1,28 @@
/* dark-mode.css */
body.dark-mode {
background-color: #121212;
color: #e0e0e0;
}
.dark-mode .card {
background-color: #1e1e1e;
color: #e0e0e0;
}
.dark-mode .form-control,
.dark-mode .form-control-lg,
.dark-mode .btn {
background-color: #2c2c2c;
color: #fff;
border-color: #444;
}
.dark-mode .btn-primary {
background-color: #3f51b5;
border-color: #3f51b5;
}
.dark-mode .note-card {
background-color: #1e1e1e;
color: #fff;
}
+82 -5
View File
@@ -4,12 +4,89 @@ $theme-colors: (
'danger': #FB1A41, /* from design */
);
$light-bg: #E6F5E9;
$dark-text: #343a40;
$btn-border-radius: 0.3rem; // Default button border radius
$btn-border-radius-sm: 0.3rem; // Small button border radius
$btn-border-radius-lg: 0.3rem;
$input-border-radius: 0.3rem; // Default input border radius
$input-border-radius-sm: 0.3rem; // Small input border radius
$input-border-radius-lg: 0.3rem;
$input-border-radius-lg: 0.3rem;
// Light theme overrides
[data-bs-theme="light"] {
/* Landing */
--bs-landing-bg: url('../assets/landing-bg.jpg');
--bs-bg-overlay: rgba(255, 255, 255, 0.85);
/* Body */
--bs-custom-body-bg: #E6F5E9; // light green
--bs-body-bg: rgba(248, 249, 250, 1);
--bs-body-color: #212529;
--bs-box-shadow: rgba(0, 0, 0, 0.1);
/* Card */
--bs-card-bg: rgba(248, 249, 250, 1);
--bs-card-color: #212529;
/* Border */
--bs-border-color: #dee2e6;
--bs-border-radius-lg: 4px;
--bs-border-new-btn-item-color: #6c757d;
--bs-border-color-translucent: rgba(248, 249, 250, 1);
/* Buttons */
--bs-btn-color: #2EB745;
--bs-btn-border-color: #2EB745;
--bs-btn-hover-color: #2EB745;
--bs-btn-new-btn-item-bg: #2EB745;
--bs-btn-new-btn-item-color: #FFF;
/* Color palette */
--bs-primary: #2EB745;
--bs-info: #2EB745;
/* sidebar header */
--bs-header-sidebar-color: #DCDCDC;
}
// card bg color: #161a1d
// secondary button bg: #272d33
// Dark theme overrides
[data-bs-theme="dark"] {
/* Landing */
--bs-landing-bg: url('../assets/landing-bg-dark.jpg');
--bs-bg-overlay: rgba(0, 0, 0, 0.95);
/* Body */
--bs-custom-body-bg: #192123; // body background color
--bs-body-bg: #161a1d; // input background color
--bs-body-color: #e0e0e0;
--bs-box-shadow: rgba(255, 255, 255, 0.9);
/* Card */
--bs-card-bg: #161a1d; // #282d33
--bs-card-color: #e0e0e0;
/* Border */
--bs-border-color: #444;
--bs-border-radius-lg: 4px;
--bs-border-new-btn-item-color: #6c757d;
--bs-border-color-translucent: #161a1d;
/* Buttons */
--bs-btn-color: #2EB745;
--bs-btn-border-color: #2EB745;
--bs-btn-hover-color: #2EB745;
--bs-btn-new-btn-item-bg: #2EB745;
--bs-btn-new-btn-item-color: #212529;
/* Inputs */
--bs-tertiary-bg: #161a1d;
/* Color palette */
--bs-primary: #2EB745;
--bs-info: #2EB745;
/* sidebar header */
--bs-header-sidebar-color: #444;
}
+53 -47
View File
@@ -1,5 +1,5 @@
import React from 'react';
import { Col, Container, Row } from 'react-bootstrap';
import { Card, Col, Container, Row } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import ContentHeader from '../../components/ContentHeader';
@@ -26,59 +26,65 @@ function About(): React.ReactNode {
<Row className="justify-content-center mb-4">
<Col xs={12}>
<div className="user-info-card">
<h2 className="mb-4 poppins-bold about-title">{t('about_app_title')}</h2>
<p className="poppins-medium">{t('about_app_description')}</p>
<h4 className="mt-4 poppins-medium">{t('about_app_features')}</h4>
<ul className="poppins-light">
<li>{t('about_app_features_one')}</li>
<li>{t('about_app_features_two')}</li>
<li>{t('about_app_features_three')}</li>
</ul>
<h4 className="mt-4 poppins-medium">{t('about_app_help_title')}</h4>
<p className="poppins-light">{t('about_app_help_description')}</p>
</div>
<Card>
<Card.Body>
<h2 className="mb-4 poppins-bold about-title">{t('about_app_title')}</h2>
<p className="poppins-medium">{t('about_app_description')}</p>
<h4 className="mt-4 poppins-medium">{t('about_app_features')}</h4>
<ul className="poppins-light">
<li>{t('about_app_features_one')}</li>
<li>{t('about_app_features_two')}</li>
<li>{t('about_app_features_three')}</li>
</ul>
<h4 className="mt-4 poppins-medium">{t('about_app_help_title')}</h4>
<p className="poppins-light">{t('about_app_help_description')}</p>
</Card.Body>
</Card>
</Col>
</Row>
<Row className="justify-content-center">
<Col xs={12} md={8}>
<div className="user-info-card min-height-350">
<h2 className="mb-4 poppins-bold about-title">{t('about_tech_title')}</h2>
<p className="poppins-light">{t('about_tech_description')}</p>
<ul className="poppins-light">
<li>{t('about_tech_list_one')}</li>
<li>{t('about_tech_list_two')}</li>
<li>{t('about_tech_list_three')}</li>
<li>{t('about_tech_list_four')}</li>
<li>{t('about_tech_list_five')}</li>
<li>{t('about_tech_list_six')}</li>
<li>{t('about_tech_list_seven')}</li>
</ul>
</div>
<Card className="min-height-350">
<Card.Body>
<h2 className="mb-4 poppins-bold about-title">{t('about_tech_title')}</h2>
<p className="poppins-light">{t('about_tech_description')}</p>
<ul className="poppins-light">
<li>{t('about_tech_list_one')}</li>
<li>{t('about_tech_list_two')}</li>
<li>{t('about_tech_list_three')}</li>
<li>{t('about_tech_list_four')}</li>
<li>{t('about_tech_list_five')}</li>
<li>{t('about_tech_list_six')}</li>
<li>{t('about_tech_list_seven')}</li>
</ul>
</Card.Body>
</Card>
</Col>
<Col sx={12} md={4} className="mt-4 mt-sm-0">
<div className="user-info-card min-height-350">
<h2 className="mb-4 poppins-bold about-title">{t('about_dev_title')}</h2>
<p className="poppins-light">
{t('about_dev_description')}
<a href="mailto:ricardompcampos@gmail.com" className="text-decoration-none">
ricardompcampos@gmail.com
</a>
{t('about_dev_description_two')}
</p>
<p className="poppins-light">
You can also
{' '}
<a
href="https://buy-me-a-coffee-two-nu.vercel.app/"
target="_blank"
rel="noreferrer"
>
Buy me a coffee
</a>
</p>
</div>
<Card className="min-height-350">
<Card.Body>
<h2 className="mb-4 poppins-bold about-title">{t('about_dev_title')}</h2>
<p className="poppins-light">
{t('about_dev_description')}
<a href="mailto:ricardompcampos@gmail.com" className="text-decoration-none">
ricardompcampos@gmail.com
</a>
{t('about_dev_description_two')}
</p>
<p className="poppins-light">
You can also
{' '}
<a
href="https://buy-me-a-coffee-two-nu.vercel.app/"
target="_blank"
rel="noreferrer"
>
Buy me a coffee
</a>
</p>
</Card.Body>
</Card>
</Col>
</Row>
</Container>
+137 -149
View File
@@ -1,7 +1,6 @@
import React, { useContext, useEffect, useState } from 'react';
import { Alert, Button, Col, Container, Form, Row } from 'react-bootstrap';
import { Alert, Button, Card, Col, Container, Form, Row } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import { ExclamationSquareFill, FileText, FloppyFill, Person, ShieldExclamation, TrashFill } from 'react-bootstrap-icons';
import DOMPurify from 'dompurify';
import { clearStorage, setDefaultLang } from '../../storage-service/storage';
import AuthContext from '../../context/AuthContext';
@@ -14,7 +13,6 @@ import { UserPatchRequest } from '../../types/UserPatchRequest';
import { UserResponse } from '../../types/UserResponse';
import ContentHeader from '../../components/ContentHeader';
import AlertError from '../../components/AlertError';
import './styles.css';
/**
* Account page component.
@@ -129,163 +127,153 @@ function Account(): React.ReactNode {
<Row>
<Col xs={12} lg={6}>
<div className="user-info-card">
<div className="title">
<Person />
{' '}
Change your info
</div>
<span className="description">
Update only what you need. Blank fields will not be updated
</span>
<Card>
<Card.Body>
<Card.Title>
Update only what you need. Blank fields will not be updated
</Card.Title>
<AlertError errorMessage={errorMessage} />
<AlertError errorMessage={errorMessage} />
<Form noValidate validated={validated} onSubmit={handleSubmit} className="mt-4">
{/* User name */}
<FormInput
labelText="First name"
iconName="Person"
required={false}
name="name"
placeholder={user?.name ? user.name : ''}
value={userName}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setUserName(e.target.value);
}}
/>
<Form noValidate validated={validated} onSubmit={handleSubmit} className="mt-4">
{/* User name */}
<FormInput
labelText="First name"
iconName="Person"
required={false}
name="name"
placeholder={user?.name ? user.name : ''}
value={userName}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setUserName(e.target.value);
}}
/>
{/* User email */}
<FormInput
labelText="Email"
iconName="At"
required={false}
name="email"
placeholder={user?.email}
value={userEmail}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setUserEmail(e.target.value);
}}
/>
{/* User email */}
<FormInput
labelText="Email"
iconName="At"
required={false}
name="email"
placeholder={user?.email}
value={userEmail}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setUserEmail(e.target.value);
}}
/>
{/* User password */}
<FormInput
labelText="Password"
iconName="Lock"
required={false}
type="password"
name="password"
value={userPassword}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setUserPassword(e.target.value);
}}
data_testid="account-password-one"
/>
{/* User password */}
<FormInput
labelText="Password"
iconName="Lock"
required={false}
type="password"
name="password"
value={userPassword}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setUserPassword(e.target.value);
}}
data_testid="account-password-one"
/>
{/* User password again */}
<FormInput
labelText="Repeat password"
iconName="Lock"
required={false}
type="password"
name="passwordAgain"
value={userPasswordAgain}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setUserPasswordAgain(e.target.value);
}}
/>
{/* User password again */}
<FormInput
labelText="Repeat password"
iconName="Lock"
required={false}
type="password"
name="passwordAgain"
value={userPasswordAgain}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setUserPasswordAgain(e.target.value);
}}
/>
<div className="d-grid">
<button
type="submit"
className="home-new-item task-note-btn"
>
<FloppyFill size={25} />
Save
</button>
</div>
</Form>
<hr />
<p>
If you want to display your picture, we support Gravatar.
Please head to
{' '}
<a href="https://gravatar.com" target="_blank" rel="noreferrer">Gravatar</a>
{' '}
to register or update your profile picture. Once updated, please wait a few
minutes to see it here.
</p>
</div>
<div className="d-grid">
<button
type="submit"
className="home-new-item task-note-btn"
>
Save
</button>
</div>
</Form>
<hr />
<p>
If you want to display your picture, we support Gravatar.
Please head to
{' '}
<a href="https://gravatar.com" target="_blank" rel="noreferrer">Gravatar</a>
{' '}
to register or update your profile picture. Once updated, please wait a few
minutes to see it here.
</p>
</Card.Body>
</Card>
</Col>
<Col xs={12} lg={6} className="mt-4 mt-lg-0">
<div className="user-info-card">
<div className="title">
<FileText />
{' '}
Change the app language
</div>
<span className="description">{t('account_app_lang_description')}</span>
<div className="mt-4 mb-2">Available languages</div>
<div>
{languages.map((lang: LangAvailable) => (
<Button
key={lang.key}
type="button"
variant="outline-primary"
className="btn-sm me-3 mb-3 d-block"
onClick={() => handleLanguage(lang.lang)}
data-testid={`language-button-${lang.lang}`}
>
{lang.lang === 'pt_br' && (
<span>🇧🇷 </span>
)}
{lang.lang === 'en' && (
<span>🇺🇸 </span>
)}
{lang.lang === 'es' && (
<span>🇪🇸 </span>
)}
{lang.lang === 'ru' && (
<span>🇷🇺 </span>
)}
{t(lang.key)}
</Button>
))}
</div>
</div>
<Card>
<Card.Body>
<Card.Title>Change the app language</Card.Title>
<span className="description">{t('account_app_lang_description')}</span>
<div className="mt-4 mb-2">Available languages</div>
<div>
{languages.map((lang: LangAvailable) => (
<Button
key={lang.key}
type="button"
variant="outline-primary"
className="btn-sm me-3 mb-3 d-block"
onClick={() => handleLanguage(lang.lang)}
data-testid={`language-button-${lang.lang}`}
>
{lang.lang === 'pt_br' && (
<span>🇧🇷 </span>
)}
{lang.lang === 'en' && (
<span>🇺🇸 </span>
)}
{lang.lang === 'es' && (
<span>🇪🇸 </span>
)}
{lang.lang === 'ru' && (
<span>🇷🇺 </span>
)}
{t(lang.key)}
</Button>
))}
</div>
</Card.Body>
</Card>
<div className="user-info-card mt-4">
<div className="title">
<ShieldExclamation />
{' '}
Your Privacy matters
</div>
<span className="description">
You decide when to delete your data
</span>
<Card className="mt-4">
<Card.Body>
<Card.Title>Your Privacy matters</Card.Title>
<span className="description">
You decide when to delete your data
</span>
<p className="mt-4 mb-2">{t('account_privacy_text')}</p>
<Button
variant="danger"
type="button"
onClick={() => setShowAlert(true)}
className="text-white btn-icon-fix"
>
<TrashFill />
{t('account_privacy_delete_btn')}
</Button>
<p className="mt-4 mb-2">{t('account_privacy_text')}</p>
<Button
variant="danger"
type="button"
onClick={() => setShowAlert(true)}
className=""
>
{t('account_privacy_delete_btn')}
</Button>
{showAlert && (
<Alert className="mt-3" variant="danger" onClose={() => setShowAlert(false)} dismissible>
<Alert.Heading>{t('account_delete_title')}</Alert.Heading>
<p>{t('account_delete_description')}</p>
<Button onClick={() => deleteAccount()} variant="outline-danger" className="btn-icon-fix">
<ExclamationSquareFill />
{t('account_delete_btn')}
</Button>
</Alert>
)}
</div>
{showAlert && (
<Alert className="mt-3" variant="danger" onClose={() => setShowAlert(false)} dismissible>
<Alert.Heading>{t('account_delete_title')}</Alert.Heading>
<p>{t('account_delete_description')}</p>
<Button onClick={() => deleteAccount()} variant="outline-danger">
{t('account_delete_btn')}
</Button>
</Alert>
)}
</Card.Body>
</Card>
</Col>
</Row>
</Container>
-19
View File
@@ -1,19 +0,0 @@
.user-info-card {
background-color: #fff;
padding: 2rem;
border-radius: 4px;
.title {
font-size: 16px;
}
.description {
font-size: 12px;
color: #6a8996;
}
}
.btn-icon-fix svg {
margin-right: 1rem;
margin-top: -3px;
}
+5 -1
View File
@@ -22,6 +22,7 @@ import TaskProgress from '../../components/TaskProgress';
import AuthContext from '../../context/AuthContext';
import ContentHeader from '../../components/ContentHeader';
import AlertError from '../../components/AlertError';
import { Search } from 'react-bootstrap-icons';
/**
* Home page component.
@@ -127,6 +128,9 @@ function Home(): React.ReactNode {
<Form noValidate validated={validated} onSubmit={handleSearch}>
<InputGroup className="mb-3">
<InputGroup.Text>
<Search />
</InputGroup.Text>
<Form.Control
pattern=".{3,}"
required
@@ -135,7 +139,7 @@ function Home(): React.ReactNode {
name="search_term"
placeholder={t('home_card_search_placeholder')}
/>
<Button type="submit" variant="primary" id="button-search">
<Button type="submit" variant="outline-secondary" id="button-search">
{t('home_card_search_btn')}
</Button>
</InputGroup>
+7 -7
View File
@@ -4,7 +4,7 @@
position: relative;
text-align: center;
color: $dark-text;
background: url('../../assets/landing-bg.jpg') no-repeat center center;
background: var(--bs-landing-bg) no-repeat center center;
background-size: cover; // Ensures the image covers the whole background
&::before {
@@ -14,7 +14,7 @@
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.85); // Slight white overlay for text readability
background-color: var(--bs-bg-overlay); // Slight overlay for text readability
z-index: 1;
}
@@ -46,14 +46,14 @@
margin-top: 5rem;
font-size: 2.5rem;
padding: 0.75rem 1rem;
color: map-get($theme-colors, 'primary');
color: var(--bs-primary);
font-weight: 600;
}
a.login {
&:hover {
font-size: 2.7rem;
color: darken(map-get($theme-colors, 'primary'), 10%);
color: var(--bs-primary);
}
}
@@ -62,11 +62,11 @@
}
.btn-outline-primary {
color: map-get($theme-colors, 'primary');
border-color: map-get($theme-colors, 'primary');
color: var(--bs-primary);
border-color: var(--bs-primary);
width: 9rem;
&:hover {
background-color: map-get($theme-colors, 'primary');
background-color: var(--bs-primary);
color: #fff;
}
}
+2 -2
View File
@@ -5,7 +5,7 @@
justify-content: center;
align-items: center;
min-height: 100vh;
background: url('../../assets/landing-bg.jpg') no-repeat center center;
background: var(--bs-landing-bg) no-repeat center center;
background-size: cover;
&::before {
@@ -15,7 +15,7 @@
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.85); // White overlay for readability
background-color: var(--bs-bg-overlay); // Slight overlay for text readability
z-index: 1;
}
+1 -2
View File
@@ -13,7 +13,7 @@ import { NoteResponse } from '../../types/NoteResponse';
import api from '../../api-service/api';
import ApiConfig from '../../api-service/apiConfig';
import { translateServerResponse } from '../../utils/TranslatorUtils';
import { PlusCircleFill, ThreeDotsVertical } from 'react-bootstrap-icons';
import { ThreeDotsVertical } from 'react-bootstrap-icons';
import NoteTitle from '../../components/NoteTitle';
import ModalMarkdown from '../../components/ModalMarkdown';
import ContentHeader from '../../components/ContentHeader';
@@ -197,7 +197,6 @@ function Note(): React.ReactNode {
type="button"
className="home-new-item task-note-btn"
>
<PlusCircleFill size={25} />
Add Notes
</button>
</div>
-8
View File
@@ -1,11 +1,3 @@
.text-done {
color: #ccc!important;
}
.text-done .btn.btn-link {
color: #ccc!important;
}
.span-line-break {
white-space: pre;
}
+3 -8
View File
@@ -1,6 +1,5 @@
import React, { useEffect, useState } from 'react';
import {
Button,
Card,
Col,
Container,
@@ -17,7 +16,6 @@ import FormInput from '../../components/FormInput';
import ModalMarkdown from '../../components/ModalMarkdown';
import AlertError from '../../components/AlertError';
import ContentHeader from '../../components/ContentHeader';
import { FloppyFill } from 'react-bootstrap-icons';
type NoteAction = 'add' | 'edit';
@@ -273,19 +271,16 @@ function NoteAdd(): React.ReactNode {
type="submit"
className="home-new-item task-note-btn mt-3"
>
<FloppyFill size={25} />
{t('note_form_submit')}
</button>
<Button
variant="outline-primary"
<button
type="button"
size="lg"
className="ms-2"
className="ms-2 home-new-item-secondary task-note-btn"
onClick={() => navigate('/notes')}
>
Cancel
</Button>
</button>
</Form>
</Card.Body>
+1 -3
View File
@@ -9,7 +9,7 @@ import {
Form,
Row
} from 'react-bootstrap';
import { PlusCircleFill, ThreeDotsVertical } from 'react-bootstrap-icons';
import { ThreeDotsVertical } from 'react-bootstrap-icons';
import { TaskResponse } from '../../types/TaskResponse';
import api from '../../api-service/api';
import ApiConfig from '../../api-service/apiConfig';
@@ -143,7 +143,6 @@ function Task(): React.ReactNode {
const classesToUse: string[] = [];
classesToUse.push('task-card');
classesToUse.push('shadow-lg');
classesToUse.push('bg-light');
if (!task.done && task.highPriority) {
classesToUse.push('high-importance');
}
@@ -191,7 +190,6 @@ function Task(): React.ReactNode {
type="button"
className="home-new-item task-note-btn"
>
<PlusCircleFill size={25} />
Add Tasks
</button>
</div>
+17 -18
View File
@@ -1,13 +1,5 @@
@import '../../styles/theme.scss';
.text-done {
color: #ccc!important;
}
.text-done .btn.btn-link {
color: #ccc!important;
}
.btn-action {
margin-right: 0.5rem;
}
@@ -18,13 +10,13 @@
}
.normal-importance {
border-left: 5px solid map-get($theme-colors, 'primary') !important;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border-left: 5px solid var(--bs-primary) !important;
box-shadow: 0 4px 8px var(--bs-box-shadow);
}
.high-importance {
border-left: 5px solid #FFD54F !important;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
box-shadow: 0 4px 8px var(--bs-box-shadow);
}
.high-importance:before {
@@ -34,7 +26,7 @@
left: 0;
border-width: 20px 20px 0 0;
border-style: solid;
border-color: #FFD54F #ffffff;
border-color: #FFD54F var(--bs-border-color);
}
.done-no-importance {
@@ -46,8 +38,8 @@
}
.task-card-footer {
background-color: #fff !important;
border-top: #fff !important;
background-color: var(--bs-card-bg) !important;
border-top: var(--bs-card-bg) !important;
}
/* Radio Buttons */
@@ -85,13 +77,13 @@
}
.custom-radio-button .form-check-input:checked {
background-color: #0d6efd;
border-color: #0d6efd;
background-color: var(--bs-primary);
border-color: var(--bs-primary);
}
.custom-radio-button .form-check-input:focus {
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
border-color: #0d6efd;
border-color: var(--bs-primary);
}
.custom-radio-button .form-check-label {
@@ -103,7 +95,7 @@
}
.custom-radio-button:hover .form-check-label {
color: #0d6efd;
color: var(--bs-primary);
}
/* Selected option text */
@@ -123,6 +115,13 @@
margin-top: -3px;
}
.btn.btn-dark {
position: fixed;
top: 20px;
right: 20px;
z-index: 1030;
}
/* Responsive adjustments */
@media (max-width: 576px) {
.radio-buttons-wrapper {
+4 -9
View File
@@ -1,6 +1,5 @@
import React, { useEffect, useState } from 'react';
import {
Button,
Card,
Col,
Container,
@@ -17,7 +16,6 @@ import { translateServerResponse } from '../../utils/TranslatorUtils';
import FormInput from '../../components/FormInput';
import ContentHeader from '../../components/ContentHeader';
import AlertError from '../../components/AlertError';
import { FloppyFill } from 'react-bootstrap-icons';
type TaskAction = 'add' | 'edit';
@@ -288,21 +286,18 @@ function TaskAdd(): React.ReactNode {
<button
type="submit"
className="home-new-item task-note-btn mt-3"
className="home-new-item task-note-btn"
>
<FloppyFill size={25} />
{t('task_form_submit')}
</button>
<Button
variant="outline-primary"
<button
type="button"
size="lg"
className="ms-2"
className="ms-2 home-new-item-secondary task-note-btn"
onClick={() => navigate('/tasks')}
>
Cancel
</Button>
</button>
</Form>
</Card.Body>
</Card>