feat: implement hide app name feature with toggle in admin settings and update branding context
Some checks failed
Build & Push Docker Image / build (push) Has been cancelled
Some checks failed
Build & Push Docker Image / build (push) Has been cancelled
This commit is contained in:
@@ -8,7 +8,7 @@ const sizes = {
|
||||
};
|
||||
|
||||
export default function BrandLogo({ size = 'md', className = '' }) {
|
||||
const { appName, hasLogo, logoUrl } = useBranding();
|
||||
const { appName, hasLogo, logoUrl, hideAppName } = useBranding();
|
||||
const s = sizes[size] || sizes.md;
|
||||
|
||||
if (hasLogo && logoUrl) {
|
||||
@@ -19,7 +19,7 @@ export default function BrandLogo({ size = 'md', className = '' }) {
|
||||
alt={appName}
|
||||
className={`${s.box} ${s.rounded} object-contain`}
|
||||
/>
|
||||
<span className={`${s.text} font-bold gradient-text`}>{appName}</span>
|
||||
{!hideAppName && <span className={`${s.text} font-bold gradient-text`}>{appName}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
126
src/components/DateTimePicker.jsx
Normal file
126
src/components/DateTimePicker.jsx
Normal file
@@ -0,0 +1,126 @@
|
||||
import ReactDatePicker from 'react-datepicker';
|
||||
import { de } from 'date-fns/locale';
|
||||
import { Calendar as CalendarIcon, Clock, X } from 'lucide-react';
|
||||
import { forwardRef } from 'react';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
|
||||
/**
|
||||
* Custom DateTimePicker that reads the app's CSS variables and
|
||||
* fully matches whatever theme is active.
|
||||
*
|
||||
* Props:
|
||||
* value – ISO datetime string (or '')
|
||||
* onChange – (isoString) => void
|
||||
* label – string
|
||||
* required – bool
|
||||
* minDate – Date | null
|
||||
* icon – 'calendar' (default) | 'clock'
|
||||
*/
|
||||
export default function DateTimePicker({
|
||||
value,
|
||||
onChange,
|
||||
label,
|
||||
required = false,
|
||||
minDate = null,
|
||||
icon = 'calendar',
|
||||
}) {
|
||||
const selected = value ? new Date(value) : null;
|
||||
|
||||
const handleChange = (date) => {
|
||||
if (!date) { onChange(''); return; }
|
||||
// Produce local datetime string yyyy-MM-ddTHH:mm
|
||||
const y = date.getFullYear();
|
||||
const mo = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const d = String(date.getDate()).padStart(2, '0');
|
||||
const h = String(date.getHours()).padStart(2, '0');
|
||||
const mi = String(date.getMinutes()).padStart(2, '0');
|
||||
onChange(`${y}-${mo}-${d}T${h}:${mi}`);
|
||||
};
|
||||
|
||||
const Icon = icon === 'clock' ? Clock : CalendarIcon;
|
||||
|
||||
return (
|
||||
<div className="dtp-wrapper">
|
||||
{label && (
|
||||
<label className="block text-sm font-medium text-th-text mb-1.5">
|
||||
{label}{required && ' *'}
|
||||
</label>
|
||||
)}
|
||||
<div className="dtp-input-wrap">
|
||||
<Icon size={15} className="dtp-icon" />
|
||||
<ReactDatePicker
|
||||
selected={selected}
|
||||
onChange={handleChange}
|
||||
showTimeSelect
|
||||
timeFormat="HH:mm"
|
||||
timeIntervals={15}
|
||||
dateFormat="dd.MM.yyyy HH:mm"
|
||||
locale={de}
|
||||
minDate={minDate}
|
||||
required={required}
|
||||
popperPlacement="bottom-start"
|
||||
popperModifiers={[
|
||||
{ name: 'offset', options: { offset: [0, 4] } },
|
||||
{ name: 'preventOverflow', options: { rootBoundary: 'viewport', tether: false, altAxis: true } },
|
||||
]}
|
||||
customInput={<CustomInput />}
|
||||
calendarClassName="dtp-calendar"
|
||||
dayClassName={(date) => 'dtp-day'}
|
||||
timeClassName={() => 'dtp-time-item'}
|
||||
renderCustomHeader={CalendarHeader}
|
||||
isClearable
|
||||
clearButtonClassName="dtp-clear-btn"
|
||||
wrapperClassName="dtp-dp-wrapper"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Custom Input ─────────────────────────────────────────────────────────────
|
||||
const CustomInput = forwardRef(({ value, onClick, onClear, ...rest }, ref) => (
|
||||
<button
|
||||
ref={ref}
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className="dtp-custom-input"
|
||||
{...rest}
|
||||
>
|
||||
<span className={value ? 'dtp-value' : 'dtp-placeholder'}>
|
||||
{value || 'Datum & Uhrzeit wählen …'}
|
||||
</span>
|
||||
</button>
|
||||
));
|
||||
CustomInput.displayName = 'CustomInput';
|
||||
|
||||
// ── Custom Header ─────────────────────────────────────────────────────────────
|
||||
function CalendarHeader({
|
||||
date,
|
||||
decreaseMonth, increaseMonth,
|
||||
prevMonthButtonDisabled, nextMonthButtonDisabled,
|
||||
}) {
|
||||
const label = date.toLocaleString('de', { month: 'long', year: 'numeric' });
|
||||
return (
|
||||
<div className="dtp-header">
|
||||
<button
|
||||
type="button"
|
||||
onClick={decreaseMonth}
|
||||
disabled={prevMonthButtonDisabled}
|
||||
className="dtp-nav-btn"
|
||||
aria-label="Vorheriger Monat"
|
||||
>
|
||||
‹
|
||||
</button>
|
||||
<span className="dtp-month-label">{label}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={increaseMonth}
|
||||
disabled={nextMonthButtonDisabled}
|
||||
className="dtp-nav-btn"
|
||||
aria-label="Nächster Monat"
|
||||
>
|
||||
›
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -13,6 +13,7 @@ export function BrandingProvider({ children }) {
|
||||
defaultTheme: null,
|
||||
imprintUrl: null,
|
||||
privacyUrl: null,
|
||||
hideAppName: false,
|
||||
});
|
||||
|
||||
const fetchBranding = useCallback(async () => {
|
||||
|
||||
@@ -373,6 +373,9 @@
|
||||
"appNameLabel": "App-Name",
|
||||
"appNameUpdated": "App-Name aktualisiert",
|
||||
"appNameUpdateFailed": "App-Name konnte nicht aktualisiert werden",
|
||||
"hideAppNameLabel": "App-Namen ausblenden",
|
||||
"hideAppNameHint": "Nur das Logo anzeigen, den App-Namen daneben ausblenden.",
|
||||
"hideAppNameFailed": "Einstellung konnte nicht gespeichert werden",
|
||||
"defaultThemeLabel": "Standard-Theme",
|
||||
"defaultThemeDesc": "Wird für nicht angemeldete Seiten (Gast-Join, Login, Startseite) verwendet, wenn keine persönliche Einstellung gesetzt ist.",
|
||||
"defaultThemeSaved": "Standard-Theme gespeichert",
|
||||
|
||||
@@ -373,6 +373,9 @@
|
||||
"appNameLabel": "App name",
|
||||
"appNameUpdated": "App name updated",
|
||||
"appNameUpdateFailed": "Could not update app name",
|
||||
"hideAppNameLabel": "Hide app name",
|
||||
"hideAppNameHint": "Only show the logo, hide the app name text next to it.",
|
||||
"hideAppNameFailed": "Could not update setting",
|
||||
"defaultThemeLabel": "Default Theme",
|
||||
"defaultThemeDesc": "Applied to unauthenticated pages (guest join, login, home) when no personal preference is set.",
|
||||
"defaultThemeSaved": "Default theme saved",
|
||||
|
||||
347
src/index.css
347
src/index.css
@@ -714,39 +714,6 @@
|
||||
transition-all duration-200;
|
||||
}
|
||||
|
||||
/* ── Styled date/time picker wrapper ── */
|
||||
.datetime-picker {
|
||||
@apply relative;
|
||||
}
|
||||
|
||||
.datetime-picker .datetime-icon {
|
||||
@apply absolute left-3.5 top-1/2 -translate-y-1/2 pointer-events-none text-th-text-s z-10;
|
||||
}
|
||||
|
||||
.datetime-picker input[type="datetime-local"],
|
||||
.datetime-picker input[type="date"],
|
||||
.datetime-picker input[type="time"] {
|
||||
@apply w-full pl-11 pr-3 py-2.5 rounded-lg
|
||||
bg-th-input text-th-text
|
||||
border border-th-input-b
|
||||
focus:outline-none focus:ring-2 focus:ring-th-ring focus:border-transparent
|
||||
transition-all duration-200;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.datetime-picker input::-webkit-calendar-picker-indicator {
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.15s;
|
||||
filter: var(--picker-icon-filter, none);
|
||||
}
|
||||
|
||||
.datetime-picker input::-webkit-calendar-picker-indicator:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.card {
|
||||
@apply bg-th-card rounded-xl border border-th-border
|
||||
shadow-th transition-all duration-200;
|
||||
@@ -772,3 +739,317 @@
|
||||
background: linear-gradient(135deg, var(--gradient-start), var(--gradient-end));
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════
|
||||
CUSTOM DATE/TIME PICKER — fully themed via CSS variables
|
||||
═══════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* ── Wrapper & trigger button ─────────────────────────────────── */
|
||||
.dtp-dp-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dtp-input-wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dtp-icon {
|
||||
position: absolute;
|
||||
left: 0.875rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--text-secondary);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dtp-custom-input {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.625rem 0.875rem 0.625rem 2.5rem;
|
||||
border-radius: 0.5rem;
|
||||
background: var(--input-bg);
|
||||
border: 1px solid var(--input-border);
|
||||
color: var(--text-primary);
|
||||
font-size: 0.875rem;
|
||||
text-align: left;
|
||||
transition: border-color 0.15s, box-shadow 0.15s;
|
||||
cursor: pointer;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.dtp-custom-input:focus,
|
||||
.dtp-custom-input:focus-visible {
|
||||
outline: none;
|
||||
border-color: transparent;
|
||||
box-shadow: 0 0 0 2px var(--ring);
|
||||
}
|
||||
|
||||
.dtp-custom-input:hover {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.dtp-placeholder {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.dtp-value {
|
||||
color: var(--text-primary);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* ── Popper & calendar container ──────────────────────────────── */
|
||||
.react-datepicker-popper {
|
||||
z-index: 100 !important;
|
||||
}
|
||||
|
||||
.dtp-calendar.react-datepicker {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 10px 25px -5px var(--shadow-color), 0 4px 10px -6px var(--shadow-color);
|
||||
font-family: inherit;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-primary);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* hide the default triangle/arrow */
|
||||
.react-datepicker-popper[data-placement^="bottom"] .react-datepicker__triangle,
|
||||
.react-datepicker-popper[data-placement^="top"] .react-datepicker__triangle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ── Header (custom rendered) ─────────────────────────────────── */
|
||||
.dtp-calendar .react-datepicker__header {
|
||||
background: var(--bg-secondary);
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.dtp-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.625rem 0.75rem 0.5rem;
|
||||
}
|
||||
|
||||
.dtp-month-label {
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-primary);
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
.dtp-nav-btn {
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 0.375rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-secondary);
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
line-height: 1;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
.dtp-nav-btn:hover:not(:disabled) {
|
||||
background: var(--hover-bg);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.dtp-nav-btn:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* ── Day names row ────────────────────────────────────────────── */
|
||||
.dtp-calendar .react-datepicker__day-names {
|
||||
background: var(--bg-secondary);
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 0.25rem 0.5rem 0.375rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.dtp-calendar .react-datepicker__day-name {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
width: 2rem;
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* ── Month / days grid ────────────────────────────────────────── */
|
||||
.dtp-calendar .react-datepicker__month {
|
||||
background: var(--card-bg);
|
||||
padding: 0.5rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.dtp-calendar .react-datepicker__week {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.dtp-calendar .react-datepicker__day {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
line-height: 2rem;
|
||||
border-radius: 0.375rem;
|
||||
color: var(--text-primary);
|
||||
font-size: 0.8125rem;
|
||||
transition: background 0.12s, color 0.12s;
|
||||
margin: 0.1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dtp-calendar .react-datepicker__day:hover:not(.react-datepicker__day--selected):not(.react-datepicker__day--disabled) {
|
||||
background: var(--hover-bg);
|
||||
color: var(--text-primary);
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
||||
.dtp-calendar .react-datepicker__day--selected,
|
||||
.dtp-calendar .react-datepicker__day--keyboard-selected {
|
||||
background: var(--accent) !important;
|
||||
color: var(--accent-text) !important;
|
||||
border-radius: 0.375rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.dtp-calendar .react-datepicker__day--today:not(.react-datepicker__day--selected) {
|
||||
font-weight: 700;
|
||||
color: var(--accent);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dtp-calendar .react-datepicker__day--today:not(.react-datepicker__day--selected)::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 3px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.dtp-calendar .react-datepicker__day--outside-month {
|
||||
color: var(--text-secondary);
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.dtp-calendar .react-datepicker__day--disabled {
|
||||
color: var(--text-secondary);
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* ── Time column ──────────────────────────────────────────────── */
|
||||
.dtp-calendar .react-datepicker__time-container {
|
||||
border-left: 1px solid var(--border);
|
||||
background: var(--card-bg);
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
.dtp-calendar .react-datepicker__time-container .react-datepicker__header {
|
||||
padding: 0.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dtp-calendar .react-datepicker__header--time {
|
||||
background: var(--bg-secondary);
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 0.5rem !important;
|
||||
}
|
||||
|
||||
.dtp-calendar .react-datepicker__header--time .react-datepicker-time__header {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.dtp-calendar .react-datepicker__time {
|
||||
background: var(--card-bg);
|
||||
}
|
||||
|
||||
.dtp-calendar .react-datepicker__time-box {
|
||||
width: 90px !important;
|
||||
}
|
||||
|
||||
.dtp-calendar .react-datepicker__time-list {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--border) transparent;
|
||||
}
|
||||
|
||||
.dtp-calendar .react-datepicker__time-list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.375rem 0 !important;
|
||||
height: auto !important;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-primary);
|
||||
transition: background 0.1s, color 0.1s;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.dtp-calendar .react-datepicker__time-list-item:hover:not(.react-datepicker__time-list-item--selected) {
|
||||
background: var(--hover-bg) !important;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.dtp-calendar .react-datepicker__time-list-item--selected {
|
||||
background: var(--accent) !important;
|
||||
color: var(--accent-text) !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.dtp-calendar .react-datepicker__time-list-item--disabled {
|
||||
color: var(--text-secondary);
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/* ── Clear button ─────────────────────────────────────────────── */
|
||||
.react-datepicker__close-icon {
|
||||
right: 0.5rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.react-datepicker__close-icon::after {
|
||||
background: var(--bg-tertiary) !important;
|
||||
color: var(--text-secondary) !important;
|
||||
font-size: 0.875rem;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
line-height: 1.25rem;
|
||||
border-radius: 0.25rem;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
}
|
||||
|
||||
.react-datepicker__close-icon:hover::after {
|
||||
background: var(--error) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
/* ── Month navigation (react-datepicker default, hidden since custom header used) ── */
|
||||
.dtp-calendar .react-datepicker__navigation {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import toast from 'react-hot-toast';
|
||||
export default function Admin() {
|
||||
const { user } = useAuth();
|
||||
const { t, language } = useLanguage();
|
||||
const { appName, hasLogo, logoUrl, defaultTheme, registrationMode, imprintUrl, privacyUrl, refreshBranding } = useBranding();
|
||||
const { appName, hasLogo, logoUrl, defaultTheme, registrationMode, imprintUrl, privacyUrl, hideAppName, refreshBranding } = useBranding();
|
||||
const navigate = useNavigate();
|
||||
const [users, setUsers] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -47,6 +47,7 @@ export default function Admin() {
|
||||
const [savingImprintUrl, setSavingImprintUrl] = useState(false);
|
||||
const [editPrivacyUrl, setEditPrivacyUrl] = useState('');
|
||||
const [savingPrivacyUrl, setSavingPrivacyUrl] = useState(false);
|
||||
const [savingHideAppName, setSavingHideAppName] = useState(false);
|
||||
|
||||
// OAuth state
|
||||
const [oauthConfig, setOauthConfig] = useState(null);
|
||||
@@ -168,6 +169,18 @@ export default function Admin() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleHideAppNameToggle = async (value) => {
|
||||
setSavingHideAppName(true);
|
||||
try {
|
||||
await api.put('/branding/hide-app-name', { hideAppName: value });
|
||||
refreshBranding();
|
||||
} catch {
|
||||
toast.error(t('admin.hideAppNameFailed'));
|
||||
} finally {
|
||||
setSavingHideAppName(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAppNameSave = async () => {
|
||||
if (!editAppName.trim()) return;
|
||||
setSavingName(true);
|
||||
@@ -447,6 +460,28 @@ export default function Admin() {
|
||||
{savingName ? <Loader2 size={14} className="animate-spin" /> : t('common.save')}
|
||||
</button>
|
||||
</div>
|
||||
{hasLogo && (
|
||||
<div className="flex items-center justify-between mt-3 p-3 rounded-lg bg-th-bg-s border border-th-border">
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-medium text-th-text">{t('admin.hideAppNameLabel')}</p>
|
||||
<p className="text-xs text-th-text-s mt-0.5">{t('admin.hideAppNameHint')}</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
disabled={savingHideAppName}
|
||||
onClick={() => handleHideAppNameToggle(!hideAppName)}
|
||||
className={`relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-th-ring focus:ring-offset-1 disabled:opacity-50 ml-4 ${
|
||||
hideAppName ? 'bg-th-accent' : 'bg-th-border'
|
||||
}`}
|
||||
aria-checked={hideAppName}
|
||||
role="switch"
|
||||
>
|
||||
<span className={`pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out ${
|
||||
hideAppName ? 'translate-x-4' : 'translate-x-0'
|
||||
}`} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import {
|
||||
ChevronLeft, ChevronRight, Plus, Calendar as CalendarIcon, Clock, Video,
|
||||
ChevronLeft, ChevronRight, Plus, Clock, Video,
|
||||
Loader2, Download, Share2, Globe, Trash2, Edit, X, UserPlus, Send, ExternalLink,
|
||||
} from 'lucide-react';
|
||||
import api from '../services/api';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { useLanguage } from '../contexts/LanguageContext';
|
||||
import Modal from '../components/Modal';
|
||||
import DateTimePicker from '../components/DateTimePicker';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
const COLORS = ['#6366f1', '#ef4444', '#f59e0b', '#10b981', '#3b82f6', '#8b5cf6', '#ec4899', '#14b8a6'];
|
||||
@@ -533,30 +534,21 @@ export default function Calendar() {
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-th-text mb-1.5">{t('calendar.startTime')} *</label>
|
||||
<div className="datetime-picker">
|
||||
<CalendarIcon size={16} className="datetime-icon" />
|
||||
<input
|
||||
type="datetime-local"
|
||||
value={form.start_time}
|
||||
onChange={e => setForm({ ...form, start_time: e.target.value })}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-th-text mb-1.5">{t('calendar.endTime')} *</label>
|
||||
<div className="datetime-picker">
|
||||
<Clock size={16} className="datetime-icon" />
|
||||
<input
|
||||
type="datetime-local"
|
||||
value={form.end_time}
|
||||
onChange={e => setForm({ ...form, end_time: e.target.value })}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DateTimePicker
|
||||
label={t('calendar.startTime')}
|
||||
value={form.start_time}
|
||||
onChange={v => setForm({ ...form, start_time: v })}
|
||||
required
|
||||
icon="calendar"
|
||||
/>
|
||||
<DateTimePicker
|
||||
label={t('calendar.endTime')}
|
||||
value={form.end_time}
|
||||
onChange={v => setForm({ ...form, end_time: v })}
|
||||
required
|
||||
icon="clock"
|
||||
minDate={form.start_time ? new Date(form.start_time) : null}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 -mt-2 text-xs text-th-text-s">
|
||||
<Globe size={12} className="flex-shrink-0" />
|
||||
|
||||
Reference in New Issue
Block a user