diff --git a/src/App.tsx b/src/App.tsx index d121ee19..8e6a7ee3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -16,7 +16,7 @@ import { getThemeConfig } from './styles/themeConfig'; import { trackClicks } from './config/mixpanel'; import { trackGAClicks } from './config/ga'; import { reloadUserProfile } from './api/user.api'; -import { setUser } from './store/slices/userSlice'; +import { fetchUserProfile, setUser } from './store/slices/userSlice'; const App: React.FC = () => { const { language } = useLanguage(); @@ -24,20 +24,11 @@ const App: React.FC = () => { const user = useAppSelector(state => state.user.user); const currentTheme = themeObject[theme]; const themeConfig = React.useMemo(() => getThemeConfig(currentTheme), [currentTheme]); - const dispatch = useAppDispatch(); - - useEffect(() => { - if (!user) { - reloadUserProfile() - .then((data) => dispatch(setUser(data))) - .catch((err) => console.error(err)); - } - }, [user, dispatch]); useEffect(() => { document.addEventListener('click', event => trackClicks(event, user)); return () => document.removeEventListener('click', event => trackClicks(event, user)); - }, [user]); + }, []); useEffect(() => { document.addEventListener('click', trackGAClicks); diff --git a/src/components/auth/SecurityCodeForm/SecurityCodeForm.styles.ts b/src/components/auth/SecurityCodeForm/SecurityCodeForm.styles.ts index 0a0c8b2f..0caef666 100644 --- a/src/components/auth/SecurityCodeForm/SecurityCodeForm.styles.ts +++ b/src/components/auth/SecurityCodeForm/SecurityCodeForm.styles.ts @@ -21,7 +21,6 @@ export const VerifyEmailDescription = styled.div` `; export const NoCodeText = styled.div` - margin-top: 1rem; color: ${({ theme }) => theme.primary}; font-size: ${({ theme }) => theme.fontSizes.xs}; font-weight: ${({ theme }) => theme.fontWeights.regular}; diff --git a/src/components/auth/SecurityCodeForm/SecurityCodeForm.tsx b/src/components/auth/SecurityCodeForm/SecurityCodeForm.tsx index 171f7481..6b2876e6 100644 --- a/src/components/auth/SecurityCodeForm/SecurityCodeForm.tsx +++ b/src/components/auth/SecurityCodeForm/SecurityCodeForm.tsx @@ -126,6 +126,7 @@ export const SecurityCodeForm: React.FC = ({ ) : ( )} +

onResendCode?.()}> {t('auth.securityCodeNoCode')} diff --git a/src/components/common/BaseButton/BaseButton.styles.ts b/src/components/common/BaseButton/BaseButton.styles.ts index 60c8211d..f6f4666f 100644 --- a/src/components/common/BaseButton/BaseButton.styles.ts +++ b/src/components/common/BaseButton/BaseButton.styles.ts @@ -4,7 +4,6 @@ import { Button as AntButton } from 'antd'; export const Button = styled(AntButton)` && { - height: 40px; padding: 0 24px; border: none; border-radius: 20px; diff --git a/src/components/common/BaseDropdown/BaseDropdown.tsx b/src/components/common/BaseDropdown/BaseDropdown.tsx index cffa5d67..29d1ccbf 100644 --- a/src/components/common/BaseDropdown/BaseDropdown.tsx +++ b/src/components/common/BaseDropdown/BaseDropdown.tsx @@ -6,9 +6,12 @@ interface BaseDropdownProps extends DropdownProps { children?: ReactNode; } -export const BaseDropdown: React.FC = ({ children, ...props }: BaseDropdownProps) => { +export const BaseDropdown: React.FC = ({ children, ...props }) => { return ( - triggerNode} {...props}> + document.body} + {...props} + > {children} ); diff --git a/src/components/common/BaseTable/BaseTable.styles.ts b/src/components/common/BaseTable/BaseTable.styles.ts index 5dcf0051..4dc06bf5 100644 --- a/src/components/common/BaseTable/BaseTable.styles.ts +++ b/src/components/common/BaseTable/BaseTable.styles.ts @@ -36,7 +36,6 @@ export const Table = styled(AntTable)` /* Remove default AntD container border for "bordered" tables */ .ant-table-container { - border-radius: 16px; /* Ensures corners are consistently rounded */ background: transparent; /* Let the parent's gradient show through */ overflow: hidden; } diff --git a/src/components/coupons/Coupons.styles.ts b/src/components/coupons/Coupons.styles.ts new file mode 100644 index 00000000..c520c4dc --- /dev/null +++ b/src/components/coupons/Coupons.styles.ts @@ -0,0 +1,59 @@ +import styled from 'styled-components'; +import { BaseCard } from '../common/BaseCard/BaseCard'; +import { BaseButton } from '../common/BaseButton/BaseButton'; + +export const NeumorphicContainer = styled.div` + background: #2a2a2a; + border-radius: 20px; + padding: 2rem; + box-shadow: + 8px 8px 16px rgba(0, 0, 0, 0.4), + -8px -8px 16px rgba(255, 255, 255, 0.05); +`; + +export const StatsCard = styled(BaseCard)` + background: linear-gradient(145deg, #2d2d2d, #262626); + border: none; + border-radius: 15px; + + /* Convex effect */ + box-shadow: + 6px 6px 12px rgba(0, 0, 0, 0.5), + -6px -6px 12px rgba(255, 255, 255, 0.05); + + &:hover { + transform: translateY(-2px); + } +`; + +export const RedeemButton = styled(BaseButton)` + /* Concave effect when not pressed */ + background: linear-gradient(145deg, #262626, #2d2d2d); + border: none; + box-shadow: + inset 4px 4px 8px rgba(0, 0, 0, 0.5), + inset -4px -4px 8px rgba(255, 255, 255, 0.05); + + /* Pressed state */ + &:active { + background: linear-gradient(145deg, #2d2d2d, #262626); + box-shadow: + inset 6px 6px 12px rgba(0, 0, 0, 0.7), + inset -6px -6px 12px rgba(255, 255, 255, 0.07); + } +`; + +export const StatsContainer = styled.div` + display: flex; + gap: 1rem; + margin-bottom: 2rem; + + /* Flat neumorphic effect */ + background: #2a2a2a; + padding: 1.5rem; + border-radius: 15px; + border: 1px solid rgba(255, 255, 255, 0.05); + box-shadow: + 4px 4px 8px rgba(0, 0, 0, 0.2), + -4px -4px 8px rgba(255, 255, 255, 0.03); +`; diff --git a/src/components/coupons/Coupons.tsx b/src/components/coupons/Coupons.tsx index 740676a9..60481157 100644 --- a/src/components/coupons/Coupons.tsx +++ b/src/components/coupons/Coupons.tsx @@ -1,15 +1,14 @@ import { useAppSelector } from '@app/hooks/reduxHooks'; -import { BaseRow } from '../common/BaseRow/BaseRow'; import { isEmailValid } from '@app/utils/utils'; -import { BaseButton } from '../common/BaseButton/BaseButton'; import { useTranslation } from 'react-i18next'; -import { BaseCol } from '../common/BaseCol/BaseCol'; import { useNavigate } from 'react-router-dom'; import { useResponsive } from '@app/hooks/useResponsive'; -import { BaseCard } from '../common/BaseCard/BaseCard'; -import { BaseSpace } from '../common/BaseSpace/BaseSpace'; import { CouponTable } from './couponTable/CouponTable'; import { EmailUpdateForm } from '../auth/EmailUpdateForm/EmailUpdateForm'; +import { NeumorphicContainer, RedeemButton, StatsContainer } from './Coupons.styles'; +import { BaseRow } from '../common/BaseRow/BaseRow'; +import { BaseCol } from '../common/BaseCol/BaseCol'; +import { BaseCard } from '../common/BaseCard/BaseCard'; export const Coupons = () => { const { t } = useTranslation(); @@ -19,23 +18,24 @@ export const Coupons = () => { const navigate = useNavigate(); const content = ( - - - {!(user?.email && isEmailValid(user.email.name)) ? ( - - * - navigate('/redeem-coupon')} style={{ padding: '0' }}> - {t('common.clickHere')} - - {t('common.addEmailWarning')} - - ) : null} - + + + + {!(user?.email && isEmailValid(user.email.name)) ? ( + + Please window.open('/redeem-coupon', '_self')}> + click here + + {t('common.addEmailWarning')} + + ) : null} + - - - - + + + + + ); if (!user) return null; diff --git a/src/components/dashboard/DashboardTerminal/DashboardTerminal.tsx b/src/components/dashboard/DashboardTerminal/DashboardTerminal.tsx index aca03df3..5398825a 100644 --- a/src/components/dashboard/DashboardTerminal/DashboardTerminal.tsx +++ b/src/components/dashboard/DashboardTerminal/DashboardTerminal.tsx @@ -42,13 +42,6 @@ const PremiumDocCard = styled(DocumentationCard)` font-weight: bold; } - /* Style the card body */ - .ant-card-body { - background-color: rgba(255, 255, 255, 0.1); - color: #fff; - padding: 1.5rem; - } - /* Add a diagonal 'PREMIUM' badge */ &::after { content: 'PREMIUM'; @@ -77,9 +70,10 @@ export const DashboardTerminal: React.FC = ({ repoDetail const user = useAppSelector((state) => state.user.user); if (!user) return null; if (!repoDetails) return null; - let shouldShowPremium = [PlanType.PREMIUM, PlanType.PRO].includes(repoDetails.plan_type); + let shouldShowPremium = ![PlanType.PREMIUM, PlanType.PRO].includes(repoDetails.plan_type); shouldShowPremium = shouldShowPremium || user.countRepoGen > 0; + return ( diff --git a/src/components/dashboard/DashboardTerminal/DocumentationCard.tsx b/src/components/dashboard/DashboardTerminal/DocumentationCard.tsx index d88e52e0..06b01c41 100644 --- a/src/components/dashboard/DashboardTerminal/DocumentationCard.tsx +++ b/src/components/dashboard/DashboardTerminal/DocumentationCard.tsx @@ -44,6 +44,84 @@ interface DocumentationCardProps { requiresPremiumAccess?: boolean; } +const ProgressIndicator = styled.div<{ progress: number }>` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: ${({ progress }) => ` + linear-gradient( + 45deg, + rgba(64, 153, 255, ${progress * 0.15}) 0%, + rgba(96, 189, 255, ${progress * 0.1}) 100% + ) + `}; + transition: background 0.3s ease; + pointer-events: none; + z-index: 0; +`; + +const CardContent = styled.div` + position: relative; + z-index: 1; +`; + +const ProgressBar = styled.div<{ progress: number }>` + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 2px; + background: linear-gradient( + to right, + rgba(64, 153, 255, 0.8) 0%, + rgba(96, 189, 255, 0.8) ${props => props.progress}%, + rgba(255, 255, 255, 0.1) ${props => props.progress}% + ); + transition: all 0.3s ease; +`; + +const ProgressOverlay = styled.div<{ progress: number }>` + position: absolute; + top: 0; + left: 0; + width: ${props => props.progress}%; + height: 100%; + background: linear-gradient( + 45deg, + rgba(64, 153, 255, 0.1) 0%, + rgba(96, 189, 255, 0.15) 100% + ); + transition: width 0.3s ease-out; + pointer-events: none; +`; + +const ProgressLine = styled.div<{ progress: number }>` + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 3px; + background: rgba(255, 255, 255, 0.1); + overflow: hidden; + + &::after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: ${props => props.progress}%; + height: 100%; + background: linear-gradient( + 90deg, + #4a90e2 0%, + #60bdff 100% + ); + transition: width 0.3s ease-out; + } +`; + export const StyledBaseCard = styled(BaseCard)` background: linear-gradient( 45deg, @@ -70,6 +148,17 @@ export const StyledBaseCard = styled(BaseCard)` -12px -12px 30px rgba(255, 255, 255, 0.12); } + /* Add a subtle pulsing animation when in progress */ + @keyframes pulse { + 0% { box-shadow: 8px 8px 20px rgba(0, 0, 0, 0.2), -8px -8px 20px rgba(255, 255, 255, 0.08); } + 50% { box-shadow: 12px 12px 30px rgba(0, 0, 0, 0.25), -12px -12px 30px rgba(255, 255, 255, 0.12); } + 100% { box-shadow: 8px 8px 20px rgba(0, 0, 0, 0.2), -8px -8px 20px rgba(255, 255, 255, 0.08); } + } + + &[data-in-progress="true"] { + animation: pulse 2s infinite ease-in-out; + } + /* Style the card body to match the neomorphic theme */ .ant-card-body { padding: 1.25rem; @@ -86,6 +175,27 @@ export const StyledBaseCard = styled(BaseCard)` @media only screen and (${media('xl')}) { padding: 2.25rem; } + /* Text color adjustment */ + .ant-typography { + color: rgba(255, 255, 255, 0.85); + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); + } + + /* Button styling within card */ + .ant-btn { + backdrop-filter: blur(5px); + border: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: + 4px 4px 8px rgba(0, 0, 0, 0.4), + -2px -2px 6px rgba(255, 255, 255, 0.05); + + &:hover { + transform: translateY(-2px); + box-shadow: + 6px 6px 12px rgba(0, 0, 0, 0.5), + -3px -3px 8px rgba(255, 255, 255, 0.07); + } + } } `; /** @@ -97,58 +207,168 @@ export const StyledBaseCard = styled(BaseCard)` */ const StyledModal = styled(Modal)` .ant-modal-content { - border-radius: 12px; - overflow: hidden; - background: #fff; - box-shadow: 0 15px 40px rgba(0, 0, 0, 0.2); + background: linear-gradient(145deg, #1f1f24, #18181d); + border-radius: 16px; + border: 1px solid rgba(255, 255, 255, 0.05); + box-shadow: + 20px 20px 60px rgba(0, 0, 0, 0.8), + -8px -8px 40px rgba(255, 255, 255, 0.03); + backdrop-filter: blur(10px); + margin: 20px; + max-width: 90vw; + width: auto !important; + + @media (min-width: 768px) { + margin: 0 auto; + width: 600px !important; + } } - /* Header with gradient background */ .ant-modal-header { - background: linear-gradient(45deg, #4a90e2, #5ac8fa); - border-bottom: none; - padding: 24px; + background: linear-gradient(145deg, #2a2a32, #1f1f24); + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + padding: 16px; + + @media (min-width: 768px) { + padding: 24px; + } } .ant-modal-title { - color: #fff; - font-size: 1.5rem; - font-weight: 600; - margin: 0; - } + color: rgba(255, 255, 255, 0.9); + font-size: 1.2rem; + font-weight: 500; + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); - /* Close icon in the header */ - .ant-modal-close-x { - line-height: 1; - color: #fff; - font-size: 16px; + @media (min-width: 768px) { + font-size: 1.5rem; + } } - /* Body styling */ .ant-modal-body { - color: #444; - font-size: 1rem; - padding: 24px; - text-align: center; + color: rgba(255, 255, 255, 0.7); + font-size: 0.9rem; + padding: 16px; + background: linear-gradient(145deg, #1f1f24, #18181d); + + @media (min-width: 768px) { + font-size: 1rem; + padding: 24px; + } + + b { + color: rgba(255, 255, 255, 0.9); + } } - /* Footer with button layout */ .ant-modal-footer { - border-top: none; - padding: 16px 24px; - background: #fafafa; + border-top: 1px solid rgba(255, 255, 255, 0.05); + padding: 12px 16px; + background: linear-gradient(145deg, #1a1a1f, #15151a); display: flex; - justify-content: center; - gap: 12px; + flex-direction: column; + align-items: center; + gap: 8px; + + @media (min-width: 768px) { + flex-direction: row; + justify-content: center; + padding: 16px 24px; + gap: 12px; + } } - /* Button styling */ - .ant-modal-footer button { - border-radius: 6px; - font-weight: 500; - display: inline-flex; - align-items: center; - justify-content: center; + .ant-btn { + width: 100%; + margin: 0; + padding: 8px 16px; + background: linear-gradient(145deg, #2a2a32, #1f1f24); + border: 1px solid rgba(255, 255, 255, 0.05); + color: rgba(255, 255, 255, 0.9); + border-radius: 8px; + position: relative; + overflow: hidden; + + /* Neumorphic convex effect */ + background: linear-gradient( + 225deg, + rgba(45, 45, 55, 1) 0%, + rgba(35, 35, 45, 1) 100% + ); + box-shadow: + 6px 6px 12px rgba(0, 0, 0, 0.5), + -2px -2px 6px rgba(255, 255, 255, 0.05), + inset -2px -2px 6px rgba(255, 255, 255, 0.05), + inset 2px 2px 6px rgba(0, 0, 0, 0.3); + + /* Add subtle top highlight for more depth */ + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient( + 90deg, + transparent, + rgba(255, 255, 255, 0.1), + transparent + ); + } + + @media (min-width: 768px) { + width: auto; + min-width: 120px; + } + + &:hover { + transform: translateY(-2px); + background: linear-gradient( + 225deg, + rgba(50, 50, 60, 1) 0%, + rgba(40, 40, 50, 1) 100% + ); + box-shadow: + 8px 8px 16px rgba(0, 0, 0, 0.6), + -3px -3px 8px rgba(255, 255, 255, 0.08), + inset -2px -2px 6px rgba(255, 255, 255, 0.08), + inset 2px 2px 6px rgba(0, 0, 0, 0.4); + } + + &:active { + transform: translateY(1px); + box-shadow: + 4px 4px 8px rgba(0, 0, 0, 0.4), + -1px -1px 4px rgba(255, 255, 255, 0.03), + inset -1px -1px 4px rgba(255, 255, 255, 0.03), + inset 1px 1px 4px rgba(0, 0, 0, 0.2); + } + + &.ant-btn-primary { + background: linear-gradient( + 225deg, + rgba(74, 144, 226, 1) 0%, + rgba(58, 123, 199, 1) 100% + ); + border: none; + + &:hover { + background: linear-gradient( + 225deg, + rgba(90, 158, 232, 1) 0%, + rgba(65, 133, 209, 1) 100% + ); + } + + &:active { + background: linear-gradient( + 225deg, + rgba(58, 123, 199, 1) 0%, + rgba(74, 144, 226, 1) 100% + ); + } + } } `; @@ -205,6 +425,7 @@ const DocumentationCard: React.FC = ({ // Handle user click for doc generation const triggerDocStringGen = useCallback(() => { + console.log('triggerDocStringGen', requiresPremiumAccess); if (requiresPremiumAccess) { setPremiumModalVisible(true); } else { @@ -323,6 +544,7 @@ const DocumentationCard: React.FC = ({ return ( + {cardTitle} @@ -361,13 +583,13 @@ const DocumentationCard: React.FC = ({ visible={premiumModalVisible} onCancel={() => setPremiumModalVisible(false)} footer={[ - , - , - , ]} diff --git a/src/components/layouts/AuthLayout/AuthLayout.styles.ts b/src/components/layouts/AuthLayout/AuthLayout.styles.ts index 3f2ca031..36bf62d4 100644 --- a/src/components/layouts/AuthLayout/AuthLayout.styles.ts +++ b/src/components/layouts/AuthLayout/AuthLayout.styles.ts @@ -154,7 +154,6 @@ export const SubmitButton = styled(BaseButton)` `; export const SocialButton = styled(BaseButton)` - /* Basic button styling */ width: 100%; margin-top: 1rem; display: flex; @@ -163,56 +162,44 @@ export const SocialButton = styled(BaseButton)` border: none; outline: none; cursor: pointer; - border-radius: 16px; /* A bit more rounding for a softer look */ + border-radius: 50px; /* More rounded corners */ /* Font styling */ font-size: ${({ theme }) => theme.fontSizes.md}; font-weight: ${({ theme }) => theme.fontWeights.semibold}; color: #fff; + text-align: center; /* Ensure text is centered */ - /* - * Use a background similar to your overall container BG. - * Adjust the gradient & pure color to match or be slightly different - * so shadows stand out. - */ - background: linear-gradient(145deg, #2e2e2e, #333333); + /* Neumorphic background */ + background: linear-gradient(-45deg, rgba(0, 0, 0, 0.22), rgba(255, 255, 255, 0.25)); + background-color: #333; /* Base color */ - /* Neumorphic outer shadows */ + /* Neumorphic shadows */ box-shadow: - 10px 10px 20px #1f1f1f, /* darker drop shadow (bottom-right) */ - -10px -10px 20px #404040; /* lighter highlight (top-left) */ + 12px 12px 16px rgba(0, 0, 0, 0.25), /* Darker shadow */ + -8px -8px 12px rgba(255, 255, 255, 0.3); /* Lighter highlight */ transition: all 0.3s ease-in-out; &:hover { - /* - * Subtle changes on hover — you can - * increase or decrease the size/blur to taste - */ box-shadow: - 12px 12px 24px #1b1b1b, - -12px -12px 24px #444444; + 14px 14px 18px rgba(0, 0, 0, 0.3), + -10px -10px 14px rgba(255, 255, 255, 0.35); transform: translateY(-2px); } &:active { - /* - * Inset shadows to simulate a “pressed” look - */ box-shadow: - inset 10px 10px 20px #1f1f1f, - inset -10px -10px 20px #404040; + inset 12px 12px 16px rgba(0, 0, 0, 0.25), + inset -8px -8px 12px rgba(255, 255, 255, 0.3); transform: translateY(0px); } - /* If you need a focus outline for accessibility: */ &:focus-visible { outline: 2px solid #666; } `; - - export const FooterWrapper = styled.div` margin-top: 1.25rem; text-align: center; diff --git a/src/components/layouts/main/MainLayout/MainLayout.tsx b/src/components/layouts/main/MainLayout/MainLayout.tsx index 4deaeee0..aff9c24a 100644 --- a/src/components/layouts/main/MainLayout/MainLayout.tsx +++ b/src/components/layouts/main/MainLayout/MainLayout.tsx @@ -1,18 +1,28 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { Header } from '@app/components/header/Header'; import MainSider from '../sider/MainSider/MainSider'; import { MainHeader } from '../MainHeader/MainHeader'; import * as S from './MainLayout.styles'; import { Outlet } from 'react-router-dom'; import { MainNavs } from '../MainNav/MainNavs'; -import { useAppSelector } from '@app/hooks/reduxHooks'; +import { useAppDispatch, useAppSelector } from '@app/hooks/reduxHooks'; import { EmailVerification } from '@app/components/dashboard/common/EmailVerification/EmailVerification'; +import { fetchUserProfile } from '@app/store/slices/userSlice'; const MainLayout: React.FC = () => { const [siderCollapsed, setSiderCollapsed] = useState(true); const userIsVerified = useAppSelector((state) => state.user.user?.isVerified); + const dispatch = useAppDispatch(); + const user = useAppSelector(state => state.user.user); + + useEffect(() => { + console.log('user', user); + dispatch(fetchUserProfile(user)); + }, [user, dispatch]); + + const toggleSider = () => setSiderCollapsed(!siderCollapsed); return ( diff --git a/src/components/layouts/main/sider/sidebarNavigation.tsx b/src/components/layouts/main/sider/sidebarNavigation.tsx index 49945eb4..2777a599 100644 --- a/src/components/layouts/main/sider/sidebarNavigation.tsx +++ b/src/components/layouts/main/sider/sidebarNavigation.tsx @@ -28,12 +28,6 @@ export const sidebarNavigation: SidebarNavigationItem[] = [ url: '/profile', icon: , }, - { - title: 'sidebar.coupons', - key: 'redeem-coupon', - url: '/redeem-coupon', - icon: , - }, { title: 'sidebar.payments', key: 'payments', diff --git a/src/components/profile/profileCard/ProfileInfo/ProfileInfo.tsx b/src/components/profile/profileCard/ProfileInfo/ProfileInfo.tsx index e4da8889..2cf13cd3 100644 --- a/src/components/profile/profileCard/ProfileInfo/ProfileInfo.tsx +++ b/src/components/profile/profileCard/ProfileInfo/ProfileInfo.tsx @@ -3,6 +3,8 @@ import { useTranslation } from 'react-i18next'; import { UserModel } from '@app/domain/UserModel'; import * as S from './ProfileInfo.styles'; import { BaseAvatar } from '@app/components/common/BaseAvatar/BaseAvatar'; +import { fetchUserProfile } from '@app/store/slices/userSlice'; +import { useAppDispatch } from '@app/hooks/reduxHooks'; interface ProfileInfoProps { profileData: UserModel | null; @@ -36,8 +38,8 @@ const countKeys = (obj: { [key: string]: any }): KeyCountResult => { export const ProfileInfo: React.FC = ({ profileData }) => { const { t } = useTranslation(); - const [fullness, setFullness] = useState(0); + // dispatch(fetchUserProfile()); useEffect(() => { if (profileData) { diff --git a/src/components/profile/profileCard/profileFormNav/nav/PersonalInfo/EmailItem/EmailItem.tsx b/src/components/profile/profileCard/profileFormNav/nav/PersonalInfo/EmailItem/EmailItem.tsx index 7671bb9a..9515e2c7 100644 --- a/src/components/profile/profileCard/profileFormNav/nav/PersonalInfo/EmailItem/EmailItem.tsx +++ b/src/components/profile/profileCard/profileFormNav/nav/PersonalInfo/EmailItem/EmailItem.tsx @@ -107,7 +107,6 @@ export const EmailItem: React.FC = ({ emailId }) => { destroyOnClose open={isVerifyModalOpen} footer={false} - closable={false} onCancel={() => setIsVerifyModalOpen(false)} > { const { t } = useTranslation(); return ( - + ); diff --git a/src/components/profile/profileCard/profileFormNav/nav/payments/Payments.tsx b/src/components/profile/profileCard/profileFormNav/nav/payments/Payments.tsx index ecfe08b9..2638e20d 100644 --- a/src/components/profile/profileCard/profileFormNav/nav/payments/Payments.tsx +++ b/src/components/profile/profileCard/profileFormNav/nav/payments/Payments.tsx @@ -1,5 +1,4 @@ import React, { useMemo } from 'react'; -import { PaymentHistory } from './paymentHistory/PaymentHistory/PaymentHistory'; import { BaseCard } from '@app/components/common/BaseCard/BaseCard'; import { useResponsive } from '@app/hooks/useResponsive'; import { BaseRow } from '@app/components/common/BaseRow/BaseRow'; @@ -15,9 +14,6 @@ export const Payments: React.FC = () => { - {/* - - */} ), [], diff --git a/src/components/profile/profileCard/profileFormNav/nav/payments/paymentHistory/PaymentHistory/PaymentHistory.tsx b/src/components/profile/profileCard/profileFormNav/nav/payments/paymentHistory/PaymentHistory/PaymentHistory.tsx index 24a89763..cf4bd9b2 100644 --- a/src/components/profile/profileCard/profileFormNav/nav/payments/paymentHistory/PaymentHistory/PaymentHistory.tsx +++ b/src/components/profile/profileCard/profileFormNav/nav/payments/paymentHistory/PaymentHistory/PaymentHistory.tsx @@ -7,6 +7,10 @@ import { ContentWrapper } from './PaymentHistory.styles'; import { BaseRow } from '@app/components/common/BaseRow/BaseRow'; import { BaseCol } from '@app/components/common/BaseCol/BaseCol'; import { PaymentHistoryTypes, getPaymentHistory } from '@app/api/payment.api'; +import { PaymentPricing } from '../../paymentPricing/PaymentPricing'; +import { BaseCard } from '@app/components/common/BaseCard/BaseCard'; +import { useResponsive } from '@app/hooks/useResponsive'; +import { BaseButtonsForm } from '@app/components/common/forms/BaseButtonsForm/BaseButtonsForm'; export const PaymentHistory: React.FC = () => { const [history, setHistory] = useState([]); @@ -18,25 +22,23 @@ export const PaymentHistory: React.FC = () => { .then((data) => setHistory(data)) .catch((err) => console.error(err)); }, []); + const { isTablet } = useResponsive(); const content = useMemo( () => ( - {t('profilePage.heading.paymentHistory')} + + {t('profilePage.heading.paymentHistory')} + - prop !== 'isEmptyHistory'}> - - - - + ), [history, t], ); - - return content; + return isTablet ? {content} : content; }; diff --git a/src/components/profile/profileCard/profileFormNav/nav/payments/paymentHistory/PaymentsTable/PaymentsTable.styles.ts b/src/components/profile/profileCard/profileFormNav/nav/payments/paymentHistory/PaymentsTable/PaymentsTable.styles.ts index e91e7bf2..53f7b3de 100644 --- a/src/components/profile/profileCard/profileFormNav/nav/payments/paymentHistory/PaymentsTable/PaymentsTable.styles.ts +++ b/src/components/profile/profileCard/profileFormNav/nav/payments/paymentHistory/PaymentsTable/PaymentsTable.styles.ts @@ -23,4 +23,11 @@ export const PaymentHistoryTable = styled(BaseTable)` font-weight: ${({ theme }) => theme.fontWeights.semibold}; font-size: ${({ theme }) => theme.fontSizes.md}; } + .ant-table-pagination { + margin: 1rem !important; + } + + .ant-pagination .ant-pagination-item-active a { + background: rgba(${({ theme }) => theme.rgb.primary}, 0.3) !important; + } `; \ No newline at end of file diff --git a/src/components/profile/profileCard/profileFormNav/nav/payments/paymentPricing/PaymentPricing.styles.ts b/src/components/profile/profileCard/profileFormNav/nav/payments/paymentPricing/PaymentPricing.styles.ts index 86dbc31d..6cefa163 100644 --- a/src/components/profile/profileCard/profileFormNav/nav/payments/paymentPricing/PaymentPricing.styles.ts +++ b/src/components/profile/profileCard/profileFormNav/nav/payments/paymentPricing/PaymentPricing.styles.ts @@ -110,15 +110,152 @@ export const PricingCard = styled(BaseCard)` `; export const CheckoutModal = styled(BaseModal)` - table { - width: 100%; - border-collapse: collapse; - - td, - th { - border: 1px solid ${({ theme }) => theme.priceLine}; - padding: 8px 14px; - text-align: start; + // Positioning + position: fixed !important; + top: 50% !important; + left: 50% !important; + transform: translate(-50%, -50%) !important; + margin: 0; + padding: 0; + max-width: 90vw; + width: auto !important; + + // Prevent flickering and ensure smooth transitions + backface-visibility: hidden; + -webkit-backface-visibility: hidden; + z-index: 1000; + + .ant-modal-content { + background: linear-gradient(145deg, #1f1f24, #18181d); + border-radius: 16px; + border: 1px solid rgba(255, 255, 255, 0.05); + box-shadow: + 20px 20px 60px rgba(0, 0, 0, 0.8), + -8px -8px 40px rgba(255, 255, 255, 0.03); + backdrop-filter: blur(10px); + + @media (min-width: 768px) { + width: 600px !important; + } + } + + .ant-modal-header { + background: linear-gradient(145deg, #2a2a32, #1f1f24); + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + padding: 16px 24px; + border-radius: 16px 16px 0 0; + } + + .ant-modal-title { + color: rgba(255, 255, 255, 0.9); + font-size: 1.25rem; + font-weight: 500; + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); + } + + .ant-modal-body { + padding: 24px; + color: rgba(255, 255, 255, 0.85); + background: linear-gradient(145deg, #1f1f24, #18181d); + + table { + width: 100%; + border-collapse: separate; + border-spacing: 0; + margin: 16px 0; + + td, th { + padding: 12px; + border: 1px solid rgba(255, 255, 255, 0.1); + background: rgba(255, 255, 255, 0.02); + color: rgba(255, 255, 255, 0.85); + transition: background 0.3s ease; + + &:first-child { + font-weight: 500; + } + } + + tfoot { + th { + background: rgba(255, 255, 255, 0.05); + font-weight: 600; + } + } } } + + .ant-modal-footer { + background: linear-gradient(145deg, #1a1a1f, #15151a); + border-top: 1px solid rgba(255, 255, 255, 0.05); + padding: 16px 24px; + border-radius: 0 0 16px 16px; + display: flex; + justify-content: flex-end; + gap: 12px; + + .ant-btn { + background: linear-gradient( + 225deg, + rgba(45, 45, 55, 1) 0%, + rgba(35, 35, 45, 1) 100% + ); + border: 1px solid rgba(255, 255, 255, 0.05); + box-shadow: + 4px 4px 8px rgba(0, 0, 0, 0.4), + -2px -2px 6px rgba(255, 255, 255, 0.05); + color: rgba(255, 255, 255, 0.9); + transition: all 0.3s ease; + + &:hover { + transform: translateY(-2px); + background: linear-gradient( + 225deg, + rgba(50, 50, 60, 1) 0%, + rgba(40, 40, 50, 1) 100% + ); + box-shadow: + 6px 6px 12px rgba(0, 0, 0, 0.5), + -3px -3px 8px rgba(255, 255, 255, 0.07); + } + + &:active { + transform: translateY(1px); + } + + &.ant-btn-primary { + background: linear-gradient( + 225deg, + rgba(74, 144, 226, 1) 0%, + rgba(58, 123, 199, 1) 100% + ); + border: none; + + &:hover { + background: linear-gradient( + 225deg, + rgba(90, 158, 232, 1) 0%, + rgba(65, 133, 209, 1) 100% + ); + } + } + } + } + + // Animation keyframes for smooth entry + @keyframes modalEnter { + from { + opacity: 0; + transform: translate(-50%, -48%) scale(0.96); + } + to { + opacity: 1; + transform: translate(-50%, -50%) scale(1); + } + } + + // Apply animation + &.ant-modal-wrap { + animation: modalEnter 0.3s ease-out; + } `; diff --git a/src/components/profile/profileCard/profileFormNav/nav/payments/paymentPricing/PaymentPricing.tsx b/src/components/profile/profileCard/profileFormNav/nav/payments/paymentPricing/PaymentPricing.tsx index 482c5339..cb5cb883 100644 --- a/src/components/profile/profileCard/profileFormNav/nav/payments/paymentPricing/PaymentPricing.tsx +++ b/src/components/profile/profileCard/profileFormNav/nav/payments/paymentPricing/PaymentPricing.tsx @@ -17,7 +17,7 @@ import pricingData from './payment.json'; import { PaymentPricingContent } from './paymentPricingContent/PaymentPricingContent'; import { BaseSpace } from '@app/components/common/BaseSpace/BaseSpace'; import { BaseSelect, Option } from '@app/components/common/selects/BaseSelect/BaseSelect'; - +import { BaseButtonsForm } from '@app/components/common/forms/BaseButtonsForm/BaseButtonsForm'; export const PaymentPricing = () => { const location = useLocation(); @@ -72,14 +72,18 @@ export const PaymentPricing = () => { const content = ( - {t('common.subscription')} - + + {t('common.subscription')} + + + {t('time.monthly')} setSwithState(!switchState)} /> {t('time.annually')}(10% discount) + {/* + diff --git a/src/locales/de/translation.json b/src/locales/de/translation.json index 3fac4834..9a8966b3 100644 --- a/src/locales/de/translation.json +++ b/src/locales/de/translation.json @@ -68,8 +68,8 @@ }, "resetPassword": "Reset password", "securityCodeNoCode": "Didn't get a verification code?", - "securityCodeTitle": "Check your mail", - "sendInstructions": "Send instructions", + "securityCodeTitle": "Check your Email", + "sendInstructions": "Send Instructions", "signup": { "agree": "I agree to the", "alreadyHaveAccount": "Already have an account? Log in", @@ -352,7 +352,7 @@ "zipcode": "Zipcode" }, "nav": { - "payment": "Payments", + "payment": "Subscriptions", "personalInfo": "Personal Info", "security": "Security Settings", "paymentsHistory": "Transactions History" diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index a3dbd814..f25cb52f 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -68,8 +68,8 @@ }, "resetPassword": "Reset password", "securityCodeNoCode": "Didn't get a verification code?", - "securityCodeTitle": "Check your mail", - "sendInstructions": "Send instructions", + "securityCodeTitle": "Check your Email", + "sendInstructions": "Send Instructions", "signup": { "agree": "I agree to the", "alreadyHaveAccount": "Already have an account? Log in", @@ -352,7 +352,7 @@ "zipcode": "Zipcode" }, "nav": { - "payment": "Payments", + "payment": "Subscriptions", "personalInfo": "Personal Info", "security": "Security Settings", "paymentsHistory": "Transactions History" diff --git a/src/pages/DashboardPages/PaymentHistoryPage/PaymentsHistoryPage.tsx b/src/pages/DashboardPages/PaymentHistoryPage/PaymentsHistoryPage.tsx index c0cf98ce..a9906d50 100644 --- a/src/pages/DashboardPages/PaymentHistoryPage/PaymentsHistoryPage.tsx +++ b/src/pages/DashboardPages/PaymentHistoryPage/PaymentsHistoryPage.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'; import { PageTitle } from '@app/components/common/PageTitle/PageTitle'; import { PaymentHistory } from '@app/components/profile/profileCard/profileFormNav/nav/payments/paymentHistory/PaymentHistory/PaymentHistory'; -const PaymentsPage: React.FC = () => { +const PaymentsHistoryPage: React.FC = () => { const { t } = useTranslation(); return ( @@ -14,4 +14,4 @@ const PaymentsPage: React.FC = () => { ); }; -export default PaymentsPage; +export default PaymentsHistoryPage; diff --git a/src/store/slices/userSlice.ts b/src/store/slices/userSlice.ts index d3a79215..b9dee130 100644 --- a/src/store/slices/userSlice.ts +++ b/src/store/slices/userSlice.ts @@ -1,8 +1,9 @@ import { createAction, createAsyncThunk, createSlice, PrepareAction } from '@reduxjs/toolkit'; import { UserModel } from '@app/domain/UserModel'; import { persistUser, readUser } from '@app/services/localStorage.service'; -import { updateUserProfile } from '@app/api/user.api'; +import { reloadUserProfile, updateUserProfile } from '@app/api/user.api'; import { UserUpdateModel } from '@app/domain/UserUpdateModel'; +import { AppDispatch } from '../store'; export interface UserState { user: UserModel | null; @@ -20,6 +21,17 @@ export const setUser = createAction>('user/setUser', (n }; }); +export const fetchUserProfile = (user: UserModel | null) => async (dispatch: AppDispatch) => { + try { + const data = await reloadUserProfile(); + if (data && JSON.stringify(data) !== JSON.stringify(user)) { + dispatch(setUser(data)); + } + } catch (err) { + console.error(err); + } +}; + export const userSlice = createSlice({ name: 'user', initialState,