Add new logos (#463)

* Add new logos

* fixup! Add new logos

* fixup! fixup! Add new logos

* Setup login screen

* fixup! Setup login screen
This commit is contained in:
Chris 2025-11-02 00:18:40 +02:00 committed by GitHub
parent 5b4a63b035
commit 26a0024207
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 298 additions and 147 deletions

View file

@ -1,7 +1,14 @@
# 📝 tududi
<p align="center">
<img src="public/wide-logo-light.png" alt="tududi" width="400">
</p>
`tududi` is the self-hosted task management tool that puts you in control. Organize your life and projects with a clear, hierarchical structure,
smart recurring tasks, and seamless Telegram integration. Get focused, stay productive, and keep your data private.
<p align="center">
<h2 align="center">Productivity made simple</p></h2>
<p align="center">Organize your life and projects with a clear, hierarchical structure,<br>
smart recurring tasks, and seamless Telegram integration.<br>
Get focused, stay productive, and keep your data private.
</p>
</p>
![Light Mode Screenshot](screenshots/all-light.png)
@ -119,6 +126,7 @@ Contributions to tududi are welcome! Whether it's bug fixes, new features, docum
6. Push to your fork and open a Pull Request
**Read our [Contributing Guide](.github/CONTRIBUTING.md) for:**
- Development setup and workflow
- Code standards and best practices
- Testing requirements

View file

@ -253,7 +253,10 @@ const App: React.FC = () => {
/>
}
/>
<Route path="/about" element={<About />} />
<Route
path="/about"
element={<About isDarkMode={isDarkMode} />}
/>
<Route
path="/admin/users"
element={

View file

@ -2,7 +2,11 @@ import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { HeartIcon } from '@heroicons/react/24/outline';
const About: React.FC = () => {
interface AboutProps {
isDarkMode?: boolean;
}
const About: React.FC<AboutProps> = ({ isDarkMode = false }) => {
const { t } = useTranslation();
const [version, setVersion] = useState<string>('0.3');
@ -33,9 +37,17 @@ const About: React.FC = () => {
<div className="max-w-2xl mx-auto">
{/* Logo and Version */}
<div className="text-center mb-8">
<h2 className="text-4xl font-bold text-gray-900 dark:text-white mb-2">
tududi
</h2>
<div className="flex justify-center mb-4">
<img
src={
isDarkMode
? '/wide-logo-light.png'
: '/wide-logo-dark.png'
}
alt="tududi"
className="h-16 w-auto"
/>
</div>
<p className="text-lg text-gray-600 dark:text-gray-400">
{t('about.version', 'Version')} {version}
</p>

View file

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import i18n from 'i18next';
import { useTranslation } from 'react-i18next';
@ -9,6 +9,16 @@ const Login: React.FC = () => {
const [error, setError] = useState<string | null>(null);
const navigate = useNavigate();
const { t } = useTranslation();
const [isDarkMode] = useState<boolean>(() => {
const storedPreference = localStorage.getItem('isDarkMode');
return storedPreference !== null
? storedPreference === 'true'
: window.matchMedia('(prefers-color-scheme: dark)').matches;
});
useEffect(() => {
document.documentElement.classList.toggle('dark', isDarkMode);
}, [isDarkMode]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
@ -45,17 +55,41 @@ const Login: React.FC = () => {
};
return (
<div className="bg-gray-100 flex flex-col items-center justify-center min-h-screen px-4">
<h1 className="text-5xl font-bold text-gray-300 mb-6">tududi</h1>
<div className="bg-white p-8 rounded-lg shadow-md w-full max-w-sm">
<>
{/* Navbar */}
<nav className="fixed top-0 left-0 right-0 z-50 text-gray-900 dark:text-white">
<div className="h-16 flex items-center px-4 sm:px-6 lg:px-8">
<img
src={
isDarkMode
? '/wide-logo-light.png'
: '/wide-logo-dark.png'
}
alt="tududi"
className="h-9 w-auto"
/>
</div>
</nav>
{/* Main Content */}
<div className="bg-gray-100 dark:bg-gray-900 min-h-screen px-4 pt-16 flex items-center justify-center">
<div className="w-full max-w-7xl flex flex-col lg:flex-row items-center justify-center gap-12 lg:gap-16">
{/* Left side - Login Form */}
<div className="w-full lg:w-auto flex flex-col items-center">
<div className="bg-white dark:bg-gray-800 p-10 rounded-lg shadow-md w-full max-w-2xl">
<h2 className="text-center text-2xl font-semibold text-gray-700 dark:text-gray-200 mb-12">
{t('auth.login', 'Login')}
</h2>
{error && (
<div className="mb-4 text-center text-red-500">{error}</div>
<div className="mb-4 text-center text-red-500">
{error}
</div>
)}
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label
htmlFor="email"
className="block text-gray-600 mb-1"
className="block text-gray-600 dark:text-gray-300 mb-1"
>
{t('auth.email', 'Email')}
</label>
@ -64,15 +98,17 @@ const Login: React.FC = () => {
id="email"
name="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
onChange={(e) =>
setEmail(e.target.value)
}
className="w-full px-4 py-2 border dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
required
/>
</div>
<div className="mb-4">
<label
htmlFor="password"
className="block text-gray-600 mb-1"
className="block text-gray-600 dark:text-gray-300 mb-1"
>
{t('auth.password', 'Password')}
</label>
@ -81,8 +117,10 @@ const Login: React.FC = () => {
id="password"
name="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
onChange={(e) =>
setPassword(e.target.value)
}
className="w-full px-4 py-2 border dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
required
/>
</div>
@ -95,6 +133,18 @@ const Login: React.FC = () => {
</form>
</div>
</div>
{/* Right side - Graphic */}
<div className="hidden lg:flex items-center justify-center">
<img
src="/login-gfx.png"
alt="Login illustration"
className="max-w-md w-full h-auto"
/>
</div>
</div>
</div>
</>
);
};

View file

@ -31,6 +31,7 @@ const Navbar: React.FC<NavbarProps> = ({
isSidebarOpen,
setIsSidebarOpen,
openTaskModal,
isDarkMode,
}) => {
const { t } = useTranslation();
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
@ -164,12 +165,17 @@ const Navbar: React.FC<NavbarProps> = ({
<Link
to="/"
className={`flex items-center no-underline text-gray-900 dark:text-white ml-2 ${isSidebarOpen ? 'sm:ml-0' : 'sm:ml-2'}`}
className={`flex items-center no-underline ml-2 ${isSidebarOpen ? 'sm:ml-0' : 'sm:ml-2'}`}
>
<span className="text-2xl font-bold">
<span className="sm:hidden">t</span>
<span className="hidden sm:inline">tududi</span>
</span>
<img
src={
isDarkMode
? '/wide-logo-light.png'
: '/wide-logo-dark.png'
}
alt="tududi"
className="h-9 w-auto"
/>
</Link>
</div>

View file

@ -1,13 +1,25 @@
import React from 'react';
const SidebarHeader: React.FC = () => {
interface SidebarHeaderProps {
isDarkMode: boolean;
}
const SidebarHeader: React.FC<SidebarHeaderProps> = ({ isDarkMode }) => {
return (
<div className="flex justify-center mb-6 mt-2">
<a
href="/"
className="flex justify-center items-center mb-2 no-underline text-gray-900 dark:text-white"
className="flex justify-center items-center mb-2 no-underline"
>
<span className="text-2xl font-bold mt-1">tududi</span>
<img
src={
isDarkMode
? '/wide-logo-light.png'
: '/wide-logo-dark.png'
}
alt="tududi"
className="h-12 w-auto"
/>
</a>
</div>
);

View file

@ -150,34 +150,33 @@
.logo {
display: flex;
align-items: center;
font-size: 1.75rem;
font-weight: 800;
color: #1e293b;
text-decoration: none;
transition: all 0.3s ease;
}
[data-theme="dark"] .logo {
color: white;
}
.logo:hover {
transform: translateY(-1px);
}
.logo-icon {
margin-right: 8px;
display: flex;
align-items: center;
justify-content: center;
.logo-image {
height: 36px;
width: auto;
}
.logo-icon svg {
color: #1e293b;
.logo-light {
display: block;
}
[data-theme="dark"] .logo-icon svg {
color: white;
.logo-dark {
display: none;
}
[data-theme="dark"] .logo-light {
display: none;
}
[data-theme="dark"] .logo-dark {
display: block;
}
.nav-links {
@ -1002,13 +1001,8 @@
padding: 15px 0;
}
.logo {
font-size: 1.5rem;
}
.logo-icon svg {
width: 24px;
height: 24px;
.logo-image {
height: 28px;
}
.nav-links {
@ -1292,13 +1286,8 @@
<div class="container">
<nav>
<a href="/" class="logo">
<div class="logo-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="28" height="28">
<circle cx="16" cy="16" r="13" stroke="currentColor" stroke-width="3.5" fill="none"/>
<path d="M10 16l4 4 8-8" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
</svg>
</div>
<span>tududi</span>
<img src="public/wide-logo-dark.png" alt="tududi" class="logo-image logo-light">
<img src="public/wide-logo-light.png" alt="tududi" class="logo-image logo-dark">
</a>
<button class="mobile-menu-toggle" onclick="toggleMobileMenu()">
<i class="fas fa-bars"></i>

83
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "tududi",
"version": "v0.84.1",
"version": "v0.85.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "tududi",
"version": "v0.84.1",
"version": "v0.85.1",
"license": "ISC",
"dependencies": {
"@dnd-kit/core": "^6.3.1",
@ -74,6 +74,7 @@
"autoprefixer": "^10.4.20",
"babel-jest": "^29.0.0",
"babel-loader": "^9.2.1",
"copy-webpack-plugin": "^13.0.1",
"cross-env": "~7.0.3",
"css-loader": "^7.1.2",
"eslint": "^8.0.0",
@ -6436,6 +6437,30 @@
"dev": true,
"license": "MIT"
},
"node_modules/copy-webpack-plugin": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-13.0.1.tgz",
"integrity": "sha512-J+YV3WfhY6W/Xf9h+J1znYuqTye2xkBUIGyTPWuBAT27qajBa5mR4f8WBmfDY3YjRftT2kqZZiLi1qf0H+UOFw==",
"dev": true,
"license": "MIT",
"dependencies": {
"glob-parent": "^6.0.1",
"normalize-path": "^3.0.0",
"schema-utils": "^4.2.0",
"serialize-javascript": "^6.0.2",
"tinyglobby": "^0.2.12"
},
"engines": {
"node": ">= 18.12.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"webpack": "^5.1.0"
}
},
"node_modules/core-js-compat": {
"version": "3.44.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.44.0.tgz",
@ -7204,9 +7229,9 @@
}
},
"node_modules/detect-libc": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=8"
@ -18414,6 +18439,54 @@
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
"license": "MIT"
},
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.3"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/tinyglobby/node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/tinyglobby/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/tmpl": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",

View file

@ -83,6 +83,7 @@
"autoprefixer": "^10.4.20",
"babel-jest": "^29.0.0",
"babel-loader": "^9.2.1",
"copy-webpack-plugin": "^13.0.1",
"cross-env": "~7.0.3",
"css-loader": "^7.1.2",
"eslint": "^8.0.0",

BIN
public/favicon-16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
public/favicon-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
public/favicon-48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

BIN
public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -1,23 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
<style>
.circle {
stroke: #4a5568;
fill: none;
}
.checkmark {
stroke: #4a5568;
fill: none;
}
@media (prefers-color-scheme: dark) {
.circle {
stroke: #e2e8f0;
}
.checkmark {
stroke: #e2e8f0;
}
}
</style>
<rect width="32" height="32" fill="transparent"/>
<circle class="circle" cx="16" cy="16" r="13" stroke-width="2"/>
<path class="checkmark" d="M10 16l4 4 8-8" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 617 B

BIN
public/icon-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

View file

@ -9,17 +9,10 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet">
<!-- SVG favicon with built-in light/dark mode support -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<!-- Light mode favicon for browsers that support it -->
<link rel="icon" type="image/x-icon" href="/favicon-light.ico" media="(prefers-color-scheme: light)">
<!-- Dark mode favicon for browsers that support it -->
<link rel="icon" type="image/x-icon" href="/favicon-dark.ico" media="(prefers-color-scheme: dark)">
<!-- Fallback favicon (medium gray - works reasonably in both modes) -->
<link rel="shortcut icon" href="/favicon.ico">
<!-- Favicon -->
<link rel="icon" type="image/x-icon" href="/favicon.ico">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16.png">
<!-- Web app manifest for PWA support -->
<link rel="manifest" href="/manifest.json">

BIN
public/login-gfx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

View file

@ -8,14 +8,29 @@
"background_color": "#ffffff",
"icons": [
{
"src": "/favicon.svg",
"sizes": "any",
"type": "image/svg+xml",
"src": "/icon-logo.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/favicon.ico",
"src": "/favicon.png",
"sizes": "32x32",
"type": "image/png"
},
{
"src": "/favicon-32.png",
"sizes": "32x32",
"type": "image/png"
},
{
"src": "/favicon-16.png",
"sizes": "16x16",
"type": "image/png"
},
{
"src": "/favicon.ico",
"sizes": "16x16 32x32 48x48",
"type": "image/x-icon"
}
]

BIN
public/wide-logo-dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
public/wide-logo-light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View file

@ -2,6 +2,7 @@ const path = require('path');
const webpack = require('webpack');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const isDevelopment = process.env.NODE_ENV !== 'production';
@ -67,6 +68,17 @@ module.exports = {
filename: 'index.html',
template: 'public/index.html'
}),
new CopyWebpackPlugin({
patterns: [
{
from: 'public',
to: '',
globOptions: {
ignore: ['**/index.html'],
},
},
],
}),
].filter(Boolean),
module: {
rules: [