From 17b4b8c790dd9bf88d26346f558f2316ceded1eb Mon Sep 17 00:00:00 2001 From: Vadym Teliatnyk Date: Sun, 19 Jul 2026 19:11:07 +0200 Subject: [PATCH 01/14] feat: refactor Button component and update calendar buttons for consistency --- src/app/[locale]/auth/page.module.css | 20 ------ src/app/[locale]/auth/page.tsx | 37 +++++++--- .../dashboard/edit/[id]/page.module.css | 58 --------------- src/app/[locale]/dashboard/edit/[id]/page.tsx | 55 +++++++++----- .../dashboard/settings/page.module.css | 21 ------ src/app/[locale]/dashboard/settings/page.tsx | 10 +-- src/app/[locale]/error.tsx | 3 +- src/app/[locale]/error/error.module.css | 12 ---- src/app/[locale]/layout.tsx | 1 + src/components/create/CreatedModal.module.css | 20 ------ src/components/create/CreatedModal.tsx | 72 ++++++++++++++----- .../dashboard/ShareModal.module.css | 28 -------- src/components/dashboard/ShareModal.tsx | 72 ++++++++++++++----- src/components/ui/Button.module.css | 47 +++++++++--- src/components/ui/Button.tsx | 52 +++++++++++--- src/i18n/it.json | 2 +- src/styles/globals.css | 2 +- 17 files changed, 267 insertions(+), 245 deletions(-) diff --git a/src/app/[locale]/auth/page.module.css b/src/app/[locale]/auth/page.module.css index ad430e5..54f8a0c 100644 --- a/src/app/[locale]/auth/page.module.css +++ b/src/app/[locale]/auth/page.module.css @@ -25,26 +25,6 @@ color: var(--fg-100); } -.authButton { - display: flex; - align-items: center; - justify-content: center; - gap: var(--spacing-12); - width: 100%; - padding: var(--spacing-14) var(--spacing-22); - border: 1px solid var(--border); - border-radius: var(--radius-sm); - font-size: var(--text-base); - color: var(--fg-150); - text-decoration: none; - transition: background-color 0.15s; - height: 48px; -} - -.authButton:hover { - background-color: var(--bg-150); -} - .agree { font-size: var(--text-lg); color: var(--fg-200); diff --git a/src/app/[locale]/auth/page.tsx b/src/app/[locale]/auth/page.tsx index 8a88fa8..eee4174 100644 --- a/src/app/[locale]/auth/page.tsx +++ b/src/app/[locale]/auth/page.tsx @@ -4,6 +4,7 @@ import { useSearchParams } from 'next/navigation' import { Suspense } from 'react' import { useTranslations } from 'next-intl' import { Link } from '@/i18n/navigation' +import Button from '@/components/ui/Button' import styles from './page.module.css' function AuthContent() { @@ -29,16 +30,36 @@ function AuthContent() {

{t('title')}

- - + +

{t('agree')}
{t('agreeOur')}{' '} diff --git a/src/app/[locale]/dashboard/edit/[id]/page.module.css b/src/app/[locale]/dashboard/edit/[id]/page.module.css index bdf6de0..fc43ad8 100644 --- a/src/app/[locale]/dashboard/edit/[id]/page.module.css +++ b/src/app/[locale]/dashboard/edit/[id]/page.module.css @@ -249,28 +249,6 @@ flex: 1; } -/* Delete button */ -.deleteBtn { - background-color: #fd585f; - color: black; - border-radius: var(--radius-full); - border: none; - padding: var(--spacing-16) var(--spacing-22); - font-size: var(--text-base); - cursor: pointer; - font-family: var(--font-primary), sans-serif; - transition: opacity 0.15s; -} - -.deleteBtn:hover { - opacity: 0.85; -} - -.deleteBtn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - /* Preview label */ .previewLabel { font-size: var(--text-base); @@ -301,30 +279,6 @@ background-color: var(--border); } -/* Apple/Google calendar buttons */ -.calButton { - display: flex; - align-items: center; - justify-content: center; - gap: var(--spacing-12); - width: 100%; - padding: var(--spacing-14) var(--spacing-22); - height: 48px; - border: 1px solid var(--border); - border-radius: var(--radius-sm); - font-size: var(--text-base); - color: var(--fg-150); - text-decoration: none; - background: none; - cursor: pointer; - transition: background-color 0.15s; - font-family: var(--font-primary), sans-serif; -} - -.calButton:hover { - background-color: var(--bg-150); -} - /* Stats */ .stats { display: flex; @@ -465,15 +419,3 @@ font-size: var(--text-lg); color: var(--fg-200); } - -.errorButton { - padding: var(--spacing-16) var(--spacing-22); - background-color: var(--btn-primary-bg); - color: var(--btn-primary-text); - border: none; - border-radius: var(--radius-full); - text-decoration: none; - font-size: var(--text-lg); - cursor: pointer; - font-family: inherit; -} diff --git a/src/app/[locale]/dashboard/edit/[id]/page.tsx b/src/app/[locale]/dashboard/edit/[id]/page.tsx index c11d2ed..8036a12 100644 --- a/src/app/[locale]/dashboard/edit/[id]/page.tsx +++ b/src/app/[locale]/dashboard/edit/[id]/page.tsx @@ -240,9 +240,9 @@ export default function EditCalendarPage() {

{et('accessDenied')}

{et('accessDeniedDesc')}

- +
) } @@ -252,9 +252,9 @@ export default function EditCalendarPage() {

{et('calendarNotFound')}

{et('calendarNotFoundDesc')}

- +
) } @@ -264,9 +264,9 @@ export default function EditCalendarPage() {

{et('serverError')}

{et('serverErrorDesc')}

- +
) } @@ -424,13 +424,14 @@ export default function EditCalendarPage() {
- + +
diff --git a/src/app/[locale]/dashboard/settings/page.module.css b/src/app/[locale]/dashboard/settings/page.module.css index b62c62c..b811555 100644 --- a/src/app/[locale]/dashboard/settings/page.module.css +++ b/src/app/[locale]/dashboard/settings/page.module.css @@ -208,23 +208,6 @@ line-height: 1.4; } -.dangerBtn { - padding: var(--spacing-16) var(--spacing-22); - background-color: #fd585f; - color: #ffffff; - border: none; - border-radius: var(--radius-full); - font-family: var(--font-primary), sans-serif; - font-size: var(--text-lg); - cursor: pointer; - white-space: nowrap; - transition: opacity 0.15s; -} - -.dangerBtn:hover { - opacity: 0.9; -} - /* Modal */ .modalContent { display: flex; @@ -278,8 +261,4 @@ flex-direction: column; align-items: stretch; } - - .dangerBtn { - width: 100%; - } } diff --git a/src/app/[locale]/dashboard/settings/page.tsx b/src/app/[locale]/dashboard/settings/page.tsx index b2acc81..a04a5ef 100644 --- a/src/app/[locale]/dashboard/settings/page.tsx +++ b/src/app/[locale]/dashboard/settings/page.tsx @@ -210,13 +210,9 @@ export default function SettingsPage() {

{t('deleteAccount')}

{t('deleteAccountDesc')}

- +
)} @@ -227,7 +223,7 @@ export default function SettingsPage() {

{t('deleteConfirm')}

- +
diff --git a/src/app/[locale]/error.tsx b/src/app/[locale]/error.tsx index c133477..44c8cc1 100644 --- a/src/app/[locale]/error.tsx +++ b/src/app/[locale]/error.tsx @@ -1,6 +1,7 @@ 'use client' import { useTranslations } from 'next-intl' +import Button from '@/components/ui/Button' import styles from './error/error.module.css' export default function Error({ reset }: { error: Error; reset: () => void }) { @@ -11,7 +12,7 @@ export default function Error({ reset }: { error: Error; reset: () => void }) {

{t('serverError')}

{t('serverErrorDesc')}

- +
) diff --git a/src/app/[locale]/error/error.module.css b/src/app/[locale]/error/error.module.css index 4eba8b4..5f00b10 100644 --- a/src/app/[locale]/error/error.module.css +++ b/src/app/[locale]/error/error.module.css @@ -25,15 +25,3 @@ color: var(--fg-200); max-width: 400px; } - -.button { - display: inline-flex; - align-items: center; - justify-content: center; - padding: var(--spacing-16) var(--spacing-22); - background-color: var(--btn-primary-bg); - color: var(--btn-primary-text); - border-radius: var(--radius-full); - font-size: var(--text-lg); - text-decoration: none; -} diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 905ac8b..1fd9a4b 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -69,6 +69,7 @@ export default async function LocaleLayout({ lang={locale} data-theme={theme} data-auth={hasAuth ? 'true' : 'false'} + className={inter.variable} suppressHydrationWarning > diff --git a/src/components/create/CreatedModal.module.css b/src/components/create/CreatedModal.module.css index 891eedc..733710a 100644 --- a/src/components/create/CreatedModal.module.css +++ b/src/components/create/CreatedModal.module.css @@ -45,26 +45,6 @@ gap: var(--spacing-12); } -.calButton { - display: flex; - align-items: center; - justify-content: center; - gap: var(--spacing-12); - padding: var(--spacing-14) var(--spacing-22); - border: 1px solid var(--border); - border-radius: var(--radius-sm); - font-size: var(--text-base); - color: var(--fg-150); - text-decoration: none; - background: none; - cursor: pointer; - transition: background-color 0.15s; -} - -.calButton:hover { - background-color: var(--bg-150); -} - .signInLink { color: var(--fg-100); text-decoration: underline; diff --git a/src/components/create/CreatedModal.tsx b/src/components/create/CreatedModal.tsx index 0136ee5..9a98198 100644 --- a/src/components/create/CreatedModal.tsx +++ b/src/components/create/CreatedModal.tsx @@ -62,26 +62,66 @@ export default function CreatedModal({ isOpen, onClose, calendar, eventCount }:
{t('addTo')}
- - - + + + - - - + +
diff --git a/src/components/dashboard/ShareModal.module.css b/src/components/dashboard/ShareModal.module.css index 5e9df13..1dffb29 100644 --- a/src/components/dashboard/ShareModal.module.css +++ b/src/components/dashboard/ShareModal.module.css @@ -40,29 +40,6 @@ gap: var(--spacing-12); } -.calButton { - display: flex; - align-items: center; - justify-content: center; - gap: var(--spacing-12); - padding: var(--spacing-14) var(--spacing-22); - border: 1px solid var(--border); - border-radius: var(--radius-sm); - font-size: var(--text-base); - color: var(--fg-150); - text-decoration: none; - transition: background-color 0.15s; - cursor: pointer; - background: none; - font-family: var(--font-primary), sans-serif; - white-space: nowrap; - height: 48px; -} - -.calButton:hover { - background-color: var(--bg-150); -} - [data-theme="light"] .iconDark { display: none; } @@ -85,11 +62,6 @@ grid-template-columns: 1fr; } - .calButton { - padding: var(--spacing-12) var(--spacing-16); - white-space: normal; - } - .footer { justify-content: stretch; } diff --git a/src/components/dashboard/ShareModal.tsx b/src/components/dashboard/ShareModal.tsx index 4c12711..ae63807 100644 --- a/src/components/dashboard/ShareModal.tsx +++ b/src/components/dashboard/ShareModal.tsx @@ -45,26 +45,66 @@ export default function ShareModal({ isOpen, onClose, calendar }: ShareModalProp
{t('addTo')}
- - - + + + - - - + +
diff --git a/src/components/ui/Button.module.css b/src/components/ui/Button.module.css index 845d296..8fe8c04 100644 --- a/src/components/ui/Button.module.css +++ b/src/components/ui/Button.module.css @@ -7,11 +7,13 @@ border-radius: var(--radius-full); font-family: var(--font-primary), sans-serif; font-size: var(--text-lg); - line-height: 1; + line-height: 1.2; white-space: nowrap; cursor: pointer; - transition: opacity 0.2s, transform 0.1s; + transition: opacity 0.2s, transform 0.1s, background-color 0.15s; border: none; + text-decoration: none; + box-sizing: border-box; } .btn:hover { @@ -25,6 +27,7 @@ .btn:disabled { opacity: 0.5; cursor: not-allowed; + transform: none; } .primary { @@ -37,6 +40,11 @@ color: var(--btn-ghost-text); } +.danger { + background-color: #fd585f; + color: #ffffff; +} + .text { background: none; color: var(--fg-100); @@ -44,25 +52,40 @@ padding: var(--spacing-12) var(--spacing-18); } +.outline { + background: none; + color: var(--fg-150); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + padding: var(--spacing-14) var(--spacing-22); + font-size: var(--text-base); +} + +.outline:hover { + opacity: 1; + background-color: var(--bg-150); +} + .fullWidth { width: 100%; } -.content { +.label { display: inline-flex; align-items: center; - gap: var(--spacing-10); + gap: var(--spacing-8); } -.content img, -.content svg { +.icon { + display: inline-flex; + align-items: center; flex-shrink: 0; - vertical-align: middle; } -.icon { - display: flex; - align-items: center; +.icon :global(svg), +.icon :global(img) { + display: block; + flex-shrink: 0; } @media (max-width: 767px) { @@ -70,4 +93,8 @@ padding: var(--spacing-12) var(--spacing-18); font-size: var(--text-base); } + + .outline { + padding: var(--spacing-14) var(--spacing-22); + } } diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx index 1445460..3811429 100644 --- a/src/components/ui/Button.tsx +++ b/src/components/ui/Button.tsx @@ -1,30 +1,64 @@ 'use client' -import { ButtonHTMLAttributes, ReactNode } from 'react' +import { AnchorHTMLAttributes, ButtonHTMLAttributes, ReactNode } from 'react' import styles from './Button.module.css' -interface ButtonProps extends ButtonHTMLAttributes { - variant?: 'primary' | 'ghost' | 'text' +type Variant = 'primary' | 'ghost' | 'text' | 'danger' | 'outline' +type IconPosition = 'left' | 'right' + +interface BaseProps { + variant?: Variant children: ReactNode icon?: ReactNode + iconPosition?: IconPosition fullWidth?: boolean } +type ButtonAsButton = BaseProps & + Omit, keyof BaseProps> & { href?: undefined } + +type ButtonAsLink = BaseProps & + Omit, keyof BaseProps> & { href: string } + +type ButtonProps = ButtonAsButton | ButtonAsLink + export default function Button({ variant = 'primary', children, icon, + iconPosition = 'right', fullWidth, className, ...props }: ButtonProps) { + const cls = [ + styles.btn, + styles[variant], + fullWidth ? styles.fullWidth : '', + className || '', + ] + .filter(Boolean) + .join(' ') + + const content = ( + <> + {icon && iconPosition === 'left' && {icon}} + {children} + {icon && iconPosition === 'right' && {icon}} + + ) + + if (props.href !== undefined) { + return ( + )}> + {content} + + ) + } + return ( - ) } diff --git a/src/i18n/it.json b/src/i18n/it.json index ec95f71..0de93dd 100644 --- a/src/i18n/it.json +++ b/src/i18n/it.json @@ -10,7 +10,7 @@ "badge": "✦ Creato per gli studenti UniBo", "title": "Il tuo orario UniBO, sempre sincronizzato", "subtitle": "Genera un link calendario personale in 30 secondi. Aggiungilo ad Apple Calendar o Google Calendar — il tuo orario si aggiorna automaticamente.", - "noAccount": "Nessun account necessario · Gratuito · Funziona con qualsiasi app calendario", + "noAccount": "Nessun account necessario · Gratuito · Funziona con qualsiasi app", "seeHow": "Scopri come funziona", "syncBadge": "{calendars} calendari sincronizzati · {events} eventi", "syncBadgePlaceholder": "Tutti gli eventi sincronizzati" diff --git a/src/styles/globals.css b/src/styles/globals.css index 2273b4e..c9cb853 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -39,7 +39,7 @@ img.emoji { } :root { - --font-primary: 'Inter Display', sans-serif; + --font-primary: var(--font-inter, 'Inter'), sans-serif; --font-mono: 'Suisse Intl', sans-serif; --radius-sm: 10px; From edb2a864829dda42a23bbb1d1681bc149180b51a Mon Sep 17 00:00:00 2001 From: Vadym Teliatnyk Date: Sun, 19 Jul 2026 19:19:56 +0200 Subject: [PATCH 02/14] feat: update layout structure and improve CSS variable handling --- src/app/[locale]/dashboard/settings/page.module.css | 1 - src/app/[locale]/layout.tsx | 4 +--- src/styles/globals.css | 1 + 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/app/[locale]/dashboard/settings/page.module.css b/src/app/[locale]/dashboard/settings/page.module.css index b811555..bdeeafe 100644 --- a/src/app/[locale]/dashboard/settings/page.module.css +++ b/src/app/[locale]/dashboard/settings/page.module.css @@ -8,7 +8,6 @@ flex: 1; padding: var(--spacing-64); overflow-y: auto; - height: 100vh; height: 100dvh; display: flex; flex-direction: column; diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 1fd9a4b..f16430b 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -72,13 +72,11 @@ export default async function LocaleLayout({ className={inter.variable} suppressHydrationWarning > - +