diff --git a/src/assets/images/login.webp b/src/assets/images/login.webp new file mode 100644 index 00000000..ec321a47 Binary files /dev/null and b/src/assets/images/login.webp differ diff --git a/src/components/apiKeys/ApiKeys.tsx b/src/components/apiKeys/ApiKeys.tsx index afcebc81..a0470a7e 100644 --- a/src/components/apiKeys/ApiKeys.tsx +++ b/src/components/apiKeys/ApiKeys.tsx @@ -13,9 +13,7 @@ export const ApiKeys: React.FC = () => { if (!user) return null; const content = ( - - ); return isDesktop ? ( diff --git a/src/components/apiKeys/apiKeysTable/ActionRow.tsx b/src/components/apiKeys/apiKeysTable/ActionRow.tsx new file mode 100644 index 00000000..2444a707 --- /dev/null +++ b/src/components/apiKeys/apiKeysTable/ActionRow.tsx @@ -0,0 +1,57 @@ +import { DeleteOutlined, CheckCircleOutlined, StopOutlined } from '@ant-design/icons'; +import React from 'react'; +import { BaseButton } from '@app/components/common/BaseButton/BaseButton'; +import { ActionRowContainer } from './ApiKeysTable.style'; +import { ApiTableRow } from '@app/api/apiToken.api'; + +interface ActionRowProps { + record: ApiTableRow; + handleToggleRow: (id: number) => void; + handleDeleteRow: (id: number) => void; + isCreating?: boolean; // If record.id === -1 + t: (key: string) => string; +} + +export const ActionRow: React.FC = ({ + record, + handleToggleRow, + handleDeleteRow, + isCreating, + t +}) => { + if (isCreating) { + // If your record has id === -1, you might show a "Create" button or something else + return ( + + handleToggleRow(record.id)}> + {t('apiTable.create')} + + + ); + } + + // Normal row: show activate/deactivate and delete + return ( + + handleToggleRow(record.id)} + icon={record.isActive ? : } + > + {record.isActive ? t('apiTable.deactivate') : t('apiTable.activate')} + + +
+ + handleDeleteRow(record.id)} + icon={} + > + {t('apiTable.delete')} + + + ); +}; diff --git a/src/components/apiKeys/apiKeysTable/ApiKeysTable.style.ts b/src/components/apiKeys/apiKeysTable/ApiKeysTable.style.ts new file mode 100644 index 00000000..b899d5a5 --- /dev/null +++ b/src/components/apiKeys/apiKeysTable/ApiKeysTable.style.ts @@ -0,0 +1,46 @@ +// ApiKeyTable.styles.ts (example filename) +import styled from 'styled-components'; + +export const ActionRowContainer = styled.div` + display: flex; + align-items: center; + justify-content: center; /* center in the column */ + gap: 1rem; /* space between buttons */ + padding: 0.5rem 1rem; + border-radius: 8px; + + /* Eye-catching gradient for the action area */ +// background: unset; + + /* Optional subtle shadow for a “raised” look */ +// box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15); + + /* A small vertical divider between activate/deactivate and delete */ + .divider { + width: 1px; + height: 24px; + background-color: rgba(255, 255, 255, 0.5); + } + + /* Style the buttons to match the gradient background */ + .action-btn { + color: #fff; + border: none; + transition: background-color 0.2s, color 0.2s; + + &:hover { + background-color: rgba(255, 255, 255, 0.15); + color: #fff; + } + + /* For “danger” or “delete” buttons, you might want a slight + variation in color or an icon. (Example: a red accent.) + */ + &.delete-btn { + background-color: rgba(255, 0, 0, 0.2); + &:hover { + background-color: rgba(255, 0, 0, 0.3); + } + } + } +`; diff --git a/src/components/apiKeys/apiKeysTable/ApiKeysTable.tsx b/src/components/apiKeys/apiKeysTable/ApiKeysTable.tsx index 9d9cc3b7..435ab7d0 100644 --- a/src/components/apiKeys/apiKeysTable/ApiKeysTable.tsx +++ b/src/components/apiKeys/apiKeysTable/ApiKeysTable.tsx @@ -15,6 +15,7 @@ import { ColumnsType } from 'antd/es/table'; import dayjs from 'dayjs'; import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { ActionRow } from './ActionRow'; const initialPagination: Pagination = { current: 1, @@ -127,37 +128,32 @@ export const ApiKeyTable: React.FC = () => { { title: t('apiTable.key'), dataIndex: 'key', - render: (text: string) => {text}, + render: (text: string, record) => { + return !record.isActive ?{text} : {text}; + }, }, { title: t('apiTable.createdAt'), dataIndex: 'createdAt', render: (text: string, record: ApiTableRow) => - record.id !== -1 ? {dayjs(text).format('DD-MM-YYYY HH:mm:ss')} : , + !record.isActive ?(record.id !== -1 ? {dayjs(text).format('DD-MM-YYYY HH:mm:ss')} : ): (record.id !== -1 ? {dayjs(text).format('DD-MM-YYYY HH:mm:ss')} : ), }, { title: t('apiTable.actions'), dataIndex: 'actions', width: '15%', - render: (_, record) => ( - - {record.id === -1 ? ( - handleCreateRow()}> - {t('apiTable.create')} - - ) : ( - <> - handleToggleRow(record.id)}> - {record.isActive ? t('apiTable.deactivate') : t('apiTable.activate')} - - handleDeleteRow(record.id)}> - {t('apiTable.delete')} - - - )} - - ), - }, + render: (_, record) => { + const isCreating = record.id === -1; + return ( + ); + }, + } ]; return ( @@ -169,6 +165,7 @@ export const ApiKeyTable: React.FC = () => { onChange={handleTableChange} scroll={{ x: 800 }} bordered + style={{ padding: '2rem' }} /> ); }; diff --git a/src/components/auth/LoginForm/LoginForm.tsx b/src/components/auth/LoginForm/LoginForm.tsx index 75d1ebec..1117f170 100644 --- a/src/components/auth/LoginForm/LoginForm.tsx +++ b/src/components/auth/LoginForm/LoginForm.tsx @@ -93,43 +93,8 @@ export const LoginForm: React.FC = () => { password: '', }} > - {t('auth.common.login')} - {t('auth.login.loginInfo')} - - - - - - - - - - {t('auth.login.rememberMe')} - - - - {t('auth.common.forgotPass')} - - - - - {t('auth.common.login')} - - + {t('auth.common.login')} +
diff --git a/src/components/auth/SignUpForm/SignUpForm.tsx b/src/components/auth/SignUpForm/SignUpForm.tsx index cb858af1..28f7929c 100644 --- a/src/components/auth/SignUpForm/SignUpForm.tsx +++ b/src/components/auth/SignUpForm/SignUpForm.tsx @@ -95,62 +95,8 @@ export const SignUpForm: React.FC = () => { requiredMark="optional" initialValues={initValues} > - {t('auth.common.signUp')} - - - - - - - - - - - - - - - - - {t('auth.signup.agree')}{' '} - - {t('auth.signup.termOfUse')} - {' '} - {t('auth.signup.and')}{' '} - - {t('auth.signup.privacyOPolicy')} - - - - - - - - {t('auth.common.signUp')} - - + {t('auth.common.signUp')} +
@@ -175,7 +121,9 @@ export const SignUpForm: React.FC = () => { {t('auth.signup.bitbucketLink')} + + {t('auth.signup.alreadyHaveAccount')}{' '} @@ -183,6 +131,18 @@ export const SignUpForm: React.FC = () => { + + + {t('auth.signup.agree')}{' '} + + {t('auth.signup.termOfUse')} + {' '} + {t('auth.signup.and')}{' '} + + {t('auth.signup.privacyOPolicy')} + + + ); diff --git a/src/components/common/BaseButton/BaseButton.styles.ts b/src/components/common/BaseButton/BaseButton.styles.ts index b94d70cf..60c8211d 100644 --- a/src/components/common/BaseButton/BaseButton.styles.ts +++ b/src/components/common/BaseButton/BaseButton.styles.ts @@ -1,105 +1,53 @@ -import styled, { css } from 'styled-components'; +// BaseButton.styles.ts +import styled from 'styled-components'; import { Button as AntButton } from 'antd'; -import { Severity } from '@app/interfaces/interfaces'; -import { colorTypeFrom } from '@app/utils/utils'; -interface BtnProps { - $severity?: Severity; - $noStyle?: boolean; -} - -export const Button = styled(AntButton)` - display: flex; - align-items: center; - justify-content: center; - gap: 0.3rem; - transition-duration: 0.3s; - font-weight: ${({ theme }) => theme.fontWeights.semibold}; - box-shadow: none; - - ${(props) => - props.$noStyle && - css` - width: unset; - padding: 0; - height: unset; - `} - - ${(props) => - !props.danger && - css` - &.ant-btn-background-ghost { - color: ${props.theme.primary}; - border-color: ${props.theme.primary}; - - &:disabled { - background-color: ${props.theme.disabledBg}; - } - } - - &.ant-btn-link { - span, - a { - text-decoration: underline; - } - } - - &:focus, - &:not(:disabled):hover { - &.ant-btn-default, - &.ant-btn-dashed { - color: ${props.theme.primary5}; - border-color: ${props.theme.primary5}; - } - } - - &:focus { - &.ant-btn-link, - &.ant-btn-background-ghost { - color: ${props.theme.primary5}; - } - - &.ant-btn-primary { - background-color: ${props.theme.primary5}; - } - - &.ant-btn-primary, - &.ant-btn-background-ghost { - border-color: ${props.theme.primary5}; - } - } - - &:not(:disabled):hover { - &.ant-btn-primary { - background-color: ${props.theme.secondary}; - } - - &.ant-btn-text, - &.ant-btn-background-ghost { - color: ${props.theme.secondary}; - background-color: transparent; - } - - &.ant-btn-primary, - &.ant-btn-background-ghost { - border-color: ${props.theme.secondary}; - } - } - - ${props.$severity && - css` - background-color: rgba(${props.theme.rgb[colorTypeFrom(props.$severity)]}, 0.2); - border-color: ${props.theme[colorTypeFrom(props.$severity)]}; - color: ${props.theme[colorTypeFrom(props.$severity)]}; - - &.ant-btn-default { - &:focus, - &:not(:disabled):hover { - background-color: ${props.theme.background}; - border-color: rgba(${props.theme.rgb[colorTypeFrom(props.$severity)]}, 0.9); - color: rgba(${props.theme.rgb[colorTypeFrom(props.$severity)]}, 0.9); - } - } - `} - `} -`; +export const Button = styled(AntButton)` + && { + height: 40px; + padding: 0 24px; + border: none; + border-radius: 20px; + background: #2a2a2a; + color: #e0e0e0; + box-shadow: 5px 5px 10px #1f1f1f, + -5px -5px 10px #353535; + transition: all 0.3s ease; + position: relative; + overflow: hidden; + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient( + 45deg, + rgba(255, 255, 255, 0.02) 0%, + rgba(0, 0, 0, 0.05) 100% + ); + } + + &:hover { + transform: translateY(-2px); + box-shadow: 8px 8px 16px #1a1a1a, + -8px -8px 16px #2a2a2a; + color: #ffffff; + } + + &:active { + box-shadow: inset 5px 5px 10px #1f1f1f, + inset -5px -5px 10px #353535; + } + + .anticon { + transition: transform 0.3s ease; + } + + &:hover .anticon { + transform: scale(1.1); + } + } +`; \ No newline at end of file diff --git a/src/components/common/BaseCard/BaseCard.styles.ts b/src/components/common/BaseCard/BaseCard.styles.ts index 0f67ef02..d64afcfa 100644 --- a/src/components/common/BaseCard/BaseCard.styles.ts +++ b/src/components/common/BaseCard/BaseCard.styles.ts @@ -11,14 +11,39 @@ interface CardInternalProps { export const Card = styled(AntCard)` display: flex; flex-direction: column; + background: linear-gradient( + 45deg, + rgba(255, 255, 255, 0.02) 0%, + rgba(255, 255, 255, 0.05) 100% + ); + border-radius: 16px; + border: none; + box-shadow: + 8px 8px 20px rgba(0, 0, 0, 0.2), + -8px -8px 20px rgba(255, 255, 255, 0.08); + transition: transform 0.3s ease, box-shadow 0.3s ease; + &:hover { + transform: translateY(-3px); + box-shadow: + 12px 12px 30px rgba(0, 0, 0, 0.25), + -12px -12px 30px rgba(255, 255, 255, 0.12); + } + + /* Adjust height if $autoHeight prop is used */ ${(props) => props.$autoHeight && css` height: 100%; `} + /* + ———————————————————————— + ANT DESIGN OVERRIDES + ———————————————————————— + */ .ant-card-head { + background-color: transparent; /* let the gradient show through */ border-bottom: 0; font-weight: ${({ theme }) => theme.fontWeights.bold}; padding-top: 15px; @@ -35,6 +60,7 @@ export const Card = styled(AntCard)` font-size: ${({ theme }) => theme.fontSizes.xxl}; } + /* Title area styles */ .ant-card-head-title { white-space: unset; overflow: unset; @@ -48,10 +74,7 @@ export const Card = styled(AntCard)` .ant-card-body { flex-grow: 1; + background-color: transparent; /* keep the gradient consistent */ padding: ${(props) => props.$padding && normalizeProp(props.$padding)}; } - - .ant-card-bordered { - border-color: ${({ theme }) => theme.split}; - } `; diff --git a/src/components/common/BaseDropdown/BaseDropdown.styles.ts b/src/components/common/BaseDropdown/BaseDropdown.styles.ts index 7183a478..c6f4fc0c 100644 --- a/src/components/common/BaseDropdown/BaseDropdown.styles.ts +++ b/src/components/common/BaseDropdown/BaseDropdown.styles.ts @@ -1,4 +1,59 @@ +// BaseDropdown.styles.ts import styled from 'styled-components'; import { Dropdown as AntDropdown } from 'antd'; -export const Dropdown = styled(AntDropdown)``; +export const Dropdown = styled(AntDropdown)` + .ant-dropdown-menu { + background: #2a2a2a; + border: none; + border-radius: 16px; + padding: 8px; + box-shadow: 8px 8px 16px #1f1f1f, + -8px -8px 16px #353535, + 0 0 24px rgba(51, 156, 253, 0.3); + transform: translateY(10px); + + &-item { + color: #e0e0e0; + border-radius: 12px; + margin: 4px 0; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; + + &:hover { + background: rgba(51, 156, 253, 0.1) !important; + transform: scale(1.02); + box-shadow: 4px 4px 8px #1f1f1f, + -4px -4px 8px #353535; + + &::after { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: linear-gradient( + 45deg, + transparent, + rgba(255, 255, 255, 0.05), + transparent + ); + animation: shine 0.8s; + } + } + } + + &-item-selected { + background: rgba(252, 180, 68, 0.15) !important; + color: #fcb444 !important; + font-weight: 500; + } + } + + @keyframes shine { + 0% { transform: translate(-100%, -100%); } + 100% { transform: translate(100%, 100%); } + } +`; \ No newline at end of file diff --git a/src/components/common/BaseSwitch/BaseSwitch.styles.ts b/src/components/common/BaseSwitch/BaseSwitch.styles.ts index 12666685..dd71e35a 100644 --- a/src/components/common/BaseSwitch/BaseSwitch.styles.ts +++ b/src/components/common/BaseSwitch/BaseSwitch.styles.ts @@ -3,7 +3,7 @@ import { Switch as AntSwitch } from 'antd'; export const Switch = styled(AntSwitch)` &.ant-switch-checked:focus { - box-shadow: 0 0 0 2px ${({ theme }) => theme.primary1}; + box-shadow: 0 0 0 2px ${({ theme }) => theme.inputPlaceholder}; } &.ant-switch[aria-checked='false'] { diff --git a/src/components/common/BaseTable/BaseTable.styles.ts b/src/components/common/BaseTable/BaseTable.styles.ts index c85638f7..ec766959 100644 --- a/src/components/common/BaseTable/BaseTable.styles.ts +++ b/src/components/common/BaseTable/BaseTable.styles.ts @@ -1,85 +1,95 @@ import styled from 'styled-components'; import { Table as AntTable } from 'antd'; +/** + * A dark-themed, neumorphic-styled table. + * We remove (or override) extra borders from AntD's "bordered" prop to prevent + * double edges, and we lighten the highlight shadows so the table isn't glaring. + */ export const Table = styled(AntTable)` - & thead .ant-table-cell { - color: ${({ theme }) => theme.primary}; - font-size: ${({ theme }) => theme.fontSizes.xs}; - line-height: 1.25rem; + /* If you don't want the border from "bordered" prop, disable it forcibly */ - & .anticon { - color: ${({ theme }) => theme.primary}; - } - } + /* Rounded corners + dark gradient background for a subtle effect */ + border-radius: 16px; + background: linear-gradient(145deg, #2a2a2a, #303030); - & tbody .ant-table-cell { - color: ${({ theme }) => theme.textMain}; - font-size: ${({ theme }) => theme.fontSizes.xs}; - line-height: 1.25rem; - } + /* Soft outward shadows for a lightly raised feel on a dark background */ + box-shadow: + 8px 8px 15px rgba(0, 0, 0, 0.5), + -8px -8px 15px rgba(255, 255, 255, 0.03); - & tbody .ant-table-row-expand-icon { - border-radius: 0.1875rem; - margin-top: 0; - } + transition: transform 0.3s ease, box-shadow 0.3s ease; - /* Override default antd selector */ - & - .ant-table-thead - > tr - > th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before { - background-color: ${({ theme }) => theme.primary}; + &:hover { + transform: translateY(-3px); + box-shadow: + 12px 12px 25px rgba(0, 0, 0, 0.5), + -12px -12px 25px rgba(255, 255, 255, 0.05); } + - & .ant-pagination-prev, - .ant-pagination-next, - .ant-pagination-jump-prev, - .ant-pagination-jump-next, - .ant-pagination-item { - min-width: 2.0625rem; - height: 2.0625rem; - line-height: 2.0625rem; - border-radius: 0; - font-size: ${({ theme }) => theme.fontSizes.xs}; - } - - & .ant-pagination-prev .ant-pagination-item-link, - .ant-pagination-next .ant-pagination-item-link { - border-radius: 0; - } + /* + ------------------------------------------------------ + ANT TABLE NESTED WRAPPER RULES + ------------------------------------------------------ + */ - & .ant-checkbox-inner { - border-radius: 0.1875rem; - height: 1.25rem; - width: 1.25rem; - border: 1px solid ${({ theme }) => theme.primary}; - } - - & .editable-row .ant-form-item-explain { - position: absolute; - top: 100%; - font-size: ${({ theme }) => theme.fontSizes.xxs}; + /* 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; } + + .ant-table-bordered { + border: 2px solid #2e2e2e; /* Remove the default border */ + border-radius: 15px; /* Ensure corners are consistently rounded */ + } - .ant-table-column-sort { - background-color: transparent; + /* + ------------------------------------------------------ + TABLE HEADER (thead) + ------------------------------------------------------ + */ + .ant-table-thead > tr > th { + background-color: #2e2e2e; /* distinct dark shade for header cells */ + color: #ccc; /* lighter text color for contrast */ + text-transform: uppercase; + font-weight: 600; + text-align: center; } - - .ant-pagination-item-container .ant-pagination-item-ellipsis { - color: ${({ theme }) => theme.disabled}; + /* + ------------------------------------------------------ + TABLE BODY (tbody) + ------------------------------------------------------ + */ + .ant-table-tbody > tr > td { + background-color: transparent; + border: none; /* remove bottom border lines */ + color: #ccc; } + + /* + ------------------------------------------------------ + for the last column only in body + ------------------------------------------------------ + */ + .ant-table-tbody > tr > td:last-child { + background: linear-gradient(135deg, #7e7e81, #595b5d); + &:hover { + background: unset; - .ant-pagination-disabled { - .ant-pagination-item-link, - .ant-pagination-item a { - color: ${({ theme }) => theme.disabled}; } } - .ant-pagination.ant-pagination-disabled { - .ant-pagination-item-link, - .ant-pagination-item a { - color: ${({ theme }) => theme.disabled}; - } + /* + Row hover: a subtle highlight + so there's feedback without strong lines + */ + .ant-table-tbody > tr:hover > td { + background: rgba(255, 255, 255, 0.05); } + + + `; diff --git a/src/components/common/forms/components/BaseFormItem/BaseFormItem.ts b/src/components/common/forms/components/BaseFormItem/BaseFormItem.ts index 20e38f0a..f0473a57 100644 --- a/src/components/common/forms/components/BaseFormItem/BaseFormItem.ts +++ b/src/components/common/forms/components/BaseFormItem/BaseFormItem.ts @@ -1,6 +1,5 @@ import styled, { css } from 'styled-components'; import { Form, FormItemProps } from 'antd'; -import { media } from '@app/utils/utils'; interface InternalFormItemProps { $isSuccess?: boolean; @@ -10,138 +9,80 @@ interface InternalFormItemProps { export type BaseFormItemProps = FormItemProps; export const BaseFormItem = styled(Form.Item as React.FC)` - .ant-input { - font-size: ${({ theme }) => theme.fontSizes.md}; - background: inherit; - border-color: ${({ theme }) => theme.borderBase}; - } - - .ant-input:focus { - box-shadow: ${({ theme }) => theme.boxShadow}; - border-color: ${({ theme }) => theme.primary5}; - } - - .ant-input:disabled { - color: ${({ theme }) => theme.disabled}; - background-color: ${({ theme }) => theme.disabledBg}; - cursor: not-allowed; - } - + /* Give the container (the Form Item) a dark background and soft shadows. */ + background: #2f2f2f; + border-radius: 10px; + padding: 1rem; /* Optional extra spacing to see the effect clearly */ + box-shadow: + 8px 8px 16px #1c1c1c, /* darker drop shadow */ + -8px -8px 16px #444444; /* lighter “highlight” shadow */ + + /* Tweak the label color for better contrast in a dark theme */ .ant-form-item-label > label { - color: ${({ theme }) => theme.primary}; + color: #ffffff; /* or use your theme color if you prefer */ font-size: ${({ theme }) => theme.fontSizes.xs}; - - .ant-form-item-optional { - color: ${({ theme }) => theme.subText}; - } } - .ant-input-group-addon:first-of-type { - font-weight: ${({ theme }) => theme.fontWeights.semibold}; - width: 5rem; - color: ${({ theme }) => theme.primary}; - - .anticon, - svg { - font-size: ${({ theme }) => theme.fontSizes.xl}; - } - - @media only screen and (${media('md')}) { - width: 5.5rem; - font-size: ${({ theme }) => theme.fontSizes.lg}; + /* Style the inputs in a neumorphic manner as well */ + .ant-input { + font-size: ${({ theme }) => theme.fontSizes.md}; + background: #2f2f2f; + color: #ffffff; + border: none; + border-radius: 8px; + /* Inset shadow to appear “pushed in” */ + box-shadow: + inset 2px 2px 5px #1c1c1c, + inset -3px -3px 5px #444444; + + /* Remove the default border and use subtle focus states */ + &:focus { + outline: none; + box-shadow: + inset 2px 2px 5px #1c1c1c, + inset -3px -3px 5px #444444, + 0 0 2px 1px #00509a; /* or your brand’s primary color */ } - @media only screen and (${media('xl')}) { - font-size: ${({ theme }) => theme.fontSizes.xxl}; + &:disabled { + cursor: not-allowed; + /* Slightly lighter or use theme’s disabled color */ + opacity: 0.6; } } - .ant-input-suffix .ant-btn { - padding: 0; - width: unset; - height: unset; - line-height: 1; - } - + /* Adjust the error message or success indicators if you like */ .ant-form-item-explain-error { - display: flex; - margin: 0.5rem 0; - line-height: 1; - + /* For a dark theme, you might want to change the “X” or background color */ &:before { content: 'X'; - display: inline-flex; - flex-shrink: 0; - align-items: center; - justify-content: center; - margin: 0 0.25rem; - color: ${({ theme }) => theme.textSecondary}; - background: ${({ theme }) => theme.error}; - border-radius: 50%; - width: 1rem; - height: 1rem; - font-size: 0.5rem; - } - - &:not(:first-of-type) { - display: none; + color: #2f2f2f; + background: #ff5252; } } + /* Success state example */ ${(props) => props.$isSuccess && css` .ant-input { - &, - &:hover { - border-color: ${({ theme }) => theme.success}; - } + border-color: #30af5b; + box-shadow: + inset 2px 2px 5px #1c1c1c, + inset -3px -3px 5px #444444, + 0 0 2px 1px #30af5b; } - .ant-form-item-control-input { - display: block; - - &::after { - content: '✓ ${props.$successText}'; - color: ${({ theme }) => theme.success}; - } + .ant-form-item-control-input::after { + content: '✓'; + color: #30af5b; + margin-left: 0.5rem; + font-size: 1em; } `} + /* Remove or hide the default feedback icons */ &.ant-form-item-has-feedback .ant-form-item-children-icon { display: none; } - - .ant-picker-suffix, - .ant-select-arrow { - font-size: ${({ theme }) => theme.fontSizes.md}; - } - - .ant-select-arrow { - width: unset; - height: unset; - top: 50%; - } - - &.ant-form-item-has-error { - .ant-input, - .ant-input-affix-wrapper, - .ant-input:hover, - .ant-input-affix-wrapper:hover { - border-color: ${({ theme }) => theme.error}; - } - } - - &.ant-form-item-has-success.ant-form-item-has-feedback { - .ant-input, - .ant-input-affix-wrapper, - .ant-input:hover, - .ant-input-affix-wrapper:hover { - border-color: ${({ theme }) => theme.success}; - } - } - - & .ant-form-item-row { - flex-wrap: inherit; - } `; diff --git a/src/components/dashboard/DashboardContent/DashboardContent.tsx b/src/components/dashboard/DashboardContent/DashboardContent.tsx index 6ab19510..0dc4b4a1 100644 --- a/src/components/dashboard/DashboardContent/DashboardContent.tsx +++ b/src/components/dashboard/DashboardContent/DashboardContent.tsx @@ -1,6 +1,5 @@ import { CrownOutlined, GithubOutlined, LinkOutlined } from '@ant-design/icons'; import { GitAppUsageType, PlanType, getGitApp } from '@app/api/analytics.api'; -import { useFeedback } from '@app/hooks/useFeedback'; import { BaseButton } from '@app/components/common/BaseButton/BaseButton'; import { BaseSpace } from '@app/components/common/BaseSpace/BaseSpace'; import { BaseSpin } from '@app/components/common/BaseSpin/BaseSpin'; @@ -12,17 +11,29 @@ import { useTranslation } from 'react-i18next'; import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; import styled, { useTheme } from 'styled-components'; import { DashboardTabs } from '../DashboardTabs/DashboardTabs'; +import { DashboardTerminal } from '../DashboardTerminal/DashboardTerminal'; -// A container to give a subtle, premium background and some padding. -const PremiumContainer = styled.div` - background: linear-gradient(to bottom, #f7f7f7, #eeeeee); - border-radius: 8px; +export const PremiumContainer = styled.div` + /* A subtle gradient with low-opacity whites for a dreamy effect */ + background: #2a2a2a; + + /* Neumorphic rounding */ + border-radius: 16px; + + /* Spacing inside the container */ padding: 1.5rem; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08); + + /* Smooth animation when hovering */ + transition: transform 0.3s ease, box-shadow 0.3s ease; + + &:hover { + transform: translateY(-3px); + box-shadow: + 12px 12px 30px rgba(0, 0, 0, 0.25), + -12px -12px 30px rgba(255, 255, 255, 0.12); + } `; -// A container for the header (title + buttons). -// Add a subtle border or bottom separation, which can help with a premium separation of sections. const HeaderContainer = styled(Flex)` padding-bottom: 1rem; border-bottom: 1px solid #d9d9d9; @@ -32,12 +43,11 @@ const HeaderContainer = styled(Flex)` const StyledBaseSpace = styled(BaseSpace)` .dashboard-crown-icon { font-size: 30px !important; - /* Metallic silver or gold accent. You can swap #c0c0c0 (silver) with #FFD700 (gold) */ - color: #c0c0c0; + color: #ffa500; /* Revert to your original orange color */ transition: color 0.3s, transform 0.3s; &:hover { - color: #a8a8a8; + color: #cc8400; /* Slightly darker orange on hover */ transform: scale(1.1); } } @@ -81,6 +91,7 @@ export const DashboardContent: React.FC = () => { const [premiumType, setPremiumType] = useState(params.has('premium')); const [repo, setRepo] = useState(); const [isLoading, setIsLoading] = useState(true); + useEffect(() => { getGitApp(orgName || '', repoName || '', 'GITHUB') @@ -107,14 +118,13 @@ export const DashboardContent: React.FC = () => { } return ( - + - + <Title level={4} style={{ marginBottom: '0', color: '#fff' }}> {orgName}/{repoName} - {/* Show the premium crown if the user is not premium */} {!premiumType && ( { )} - {/* Netlify link (if available) */} { /> - {/* GitHub link */} { - - {/* The rest of your Dashboard content */} - {repo && } + ); }; diff --git a/src/components/dashboard/DashboardList/ListAllRepos/ListAllRepos.Styles.ts b/src/components/dashboard/DashboardList/ListAllRepos/ListAllRepos.Styles.ts index 30573a21..a04d0885 100644 --- a/src/components/dashboard/DashboardList/ListAllRepos/ListAllRepos.Styles.ts +++ b/src/components/dashboard/DashboardList/ListAllRepos/ListAllRepos.Styles.ts @@ -1,33 +1,151 @@ +// ListAllRepos.Styles.ts import { BaseCard } from '@app/components/common/BaseCard/BaseCard'; import { media } from '@app/utils/utils'; import styled from 'styled-components'; export const OuterBaseCard = styled(BaseCard)` + background: #2a2a2a; + border: none; + background: linear-gradient(145deg, #1a1a1a, #2d2d2d); + border-radius: 20px; + border: none; + box-shadow: + 8px 8px 16px rgba(0, 0, 0, 0.2), + -8px -8px 16px rgba(255, 255, 255, 0.05); + transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); + + &:hover { + box-shadow: + 12px 12px 24px rgba(0, 0, 0, 0.25), + -12px -12px 24px rgba(255, 255, 255, 0.08); + transform: translateY(-2px); + } + + .ant-card-head { + border-bottom: none; + padding: 16px 24px; + font-size: 1.25rem; + color: #e0e0e0; + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3); + } + .ant-card-body { + padding: 24px; @media only screen and (${media('xl')}) { - padding: 24px 48px 48px; - flex-grow: 0; + padding: 32px 48px !important; } } `; export const InnerBaseCard = styled(BaseCard)` cursor: pointer; - border-color: #339cfd !important; + position: relative; + overflow: hidden; + border-radius: 20px; + border: none; + /* A dark-ish base color with a subtle gradient */ + background-color: #2a2a2a; + background: linear-gradient(145deg, #292929, #2e2e2e); + + /* Outer + inset shadows for a “soft pillow” look */ + box-shadow: + /* Outer, bottom-right shadow (darker) */ + 10px 10px 22px rgba(0, 0, 0, 0.6), + /* Outer, top-left highlight */ + -10px -10px 22px rgba(60, 60, 60, 0.06), + /* Inset shadow to give a velvety dip */ + inset 0 0 10px rgba(0, 0, 0, 0.3); + + transition: all 0.3s ease; + + /* + * Subtle texture overlay. + * You can swap this out for your own pattern, + * or remove it if you prefer a clean look. + */ + &::before { + content: ''; + position: absolute; + inset: 0; + /* Very subtle radial highlights in corners */ + background: + radial-gradient(circle at top left, rgba(255, 255, 255, 0.02), transparent 60%), + radial-gradient(circle at bottom right, rgba(255, 255, 255, 0.02), transparent 60%); + background-repeat: no-repeat; + pointer-events: none; + z-index: 1; + /* Mix-blend or overlay can enhance the “soft” effect */ + mix-blend-mode: overlay; + } + + /* Position content above the texture layer */ .ant-card-body { - @media only screen and (${media('xl')}) { - padding: 30px 16px; - } + position: relative; + z-index: 2; + padding: 16px; + } + + span { + color: #ffffff; + opacity: 0.9; + font-weight: 500; + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3); } + /* Hover: slightly scale up & adjust shadow for “lift” */ + &:hover { + transform: scale(1.02); + box-shadow: + 12px 12px 28px rgba(0, 0, 0, 0.65), + -12px -12px 28px rgba(60, 60, 60, 0.06), + inset 0 0 12px rgba(0, 0, 0, 0.35); + } + + /* Active: pop it “inward” to simulate a pressed state */ + &:active { + transform: scale(0.98); + box-shadow: + inset 10px 10px 20px rgba(0, 0, 0, 0.5), + inset -10px -10px 20px rgba(60, 60, 60, 0.05); + } + + /* Premium style overrides: keep your existing logic */ &.premium-card { - border-color: rgb(252, 180, 68) !important; - background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 21 21'%3E%3Cg fill='none' fill-rule='evenodd' stroke='%23fcb444' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m15.5 4l3 4l-8 10l-8-10l3.009-4zm-13 4h16m-11 0l3 10m3-10l-3 10'/%3E%3Cpath d='M5.509 4L7.5 8l3-4l3 4l2-4'/%3E%3C/g%3E%3C/svg%3E") - no-repeat; - background-position: calc(100% - 12px) 12px; - background-size: 20px; + border: 1px solid rgba(252, 180, 68, 0.3) !important; + box-shadow: + 0 0 15px rgba(252, 180, 68, 0.3), + 10px 10px 22px rgba(0, 0, 0, 0.6), + -10px -10px 22px rgba(60, 60, 60, 0.06), + inset 0 0 10px rgba(0, 0, 0, 0.3); + + &::after { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: linear-gradient( + 45deg, + rgba(252, 180, 68, 0.1) 0%, + rgba(252, 180, 68, 0) 50% + ); + z-index: 1; + /* uncomment if you want an animated "shine" + animation: shine 3s infinite; + */ + } + } + + @keyframes shine { + 0% { + transform: translate(0, 0); + } + 100% { + transform: translate(100%, 100%); + } } `; @@ -36,20 +154,31 @@ export const TransparentCard = styled.div` width: 100%; height: 100%; inset: 0; - background: #339cfd50; - border-radius: 7px; + background: rgba(51, 156, 253, 0.1); + border-radius: 15px; display: flex; align-items: center; justify-content: center; opacity: 0; - backdrop-filter: blur(2px); - transition: opacity 350ms ease-in; + backdrop-filter: blur(4px); + transition: all 0.3s ease; + z-index: 2; &:hover { opacity: 1; } &.premium-card { - background: #fcb44480; + background: rgba(252, 180, 68, 0.15); + + .anticon { + color: #fcb444 !important; + filter: drop-shadow(0 0 8px rgba(252, 180, 68, 0.6)); + } } -`; + + .anticon { + color: #ffffff !important; + filter: drop-shadow(0 0 4px rgba(255, 255, 255, 0.2)); + } +`; \ No newline at end of file diff --git a/src/components/dashboard/DashboardTerminal/DashboardTerminal.tsx b/src/components/dashboard/DashboardTerminal/DashboardTerminal.tsx index a8456a99..aca03df3 100644 --- a/src/components/dashboard/DashboardTerminal/DashboardTerminal.tsx +++ b/src/components/dashboard/DashboardTerminal/DashboardTerminal.tsx @@ -16,7 +16,6 @@ import { useAppSelector } from '@app/hooks/reduxHooks'; const PremiumDocCard = styled(DocumentationCard)` /* Background gradient and subtle rounded corners */ - background: linear-gradient(135deg, #f6d365 0%, #fda085 100%); border: none; border-radius: 12px; overflow: hidden; @@ -77,6 +76,7 @@ interface DashboardTerminalProps { export const DashboardTerminal: React.FC = ({ repoDetails }) => { 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); shouldShowPremium = shouldShowPremium || user.countRepoGen > 0; @@ -85,24 +85,14 @@ export const DashboardTerminal: React.FC = ({ repoDetail return ( - {!shouldShowPremium ? ( - ) : ( - - )} diff --git a/src/components/dashboard/DashboardTerminal/DocumentationCard.tsx b/src/components/dashboard/DashboardTerminal/DocumentationCard.tsx index 3e442832..d88e52e0 100644 --- a/src/components/dashboard/DashboardTerminal/DocumentationCard.tsx +++ b/src/components/dashboard/DashboardTerminal/DocumentationCard.tsx @@ -23,6 +23,7 @@ import { exchangeRateApi, getLocationApi, GitAppUsageType, PlanType } from '@app import { paypalStandardCheckout } from '@app/api/paypal.api'; import { cashfreeStandardCheckout } from '@app/api/cashfree.api'; import { convertPrices } from '@app/utils/utils'; +import { color } from 'echarts'; interface DocumentationCardProps { title: string; @@ -43,9 +44,36 @@ interface DocumentationCardProps { requiresPremiumAccess?: boolean; } -const StyledBaseCard = styled(BaseCard)` +export const StyledBaseCard = styled(BaseCard)` + background: linear-gradient( + 45deg, + rgba(255, 255, 255, 0.02) 0%, + rgba(255, 255, 255, 0.05) 100% + ); + border-radius: 16px; + border: none; + overflow: hidden; + position: relative; + + /* Smooth hover animation for a “lifting” effect */ + transition: transform 0.3s ease, box-shadow 0.3s ease; + + /* The core “neumorphic” shadows: one darker, one lighter */ + box-shadow: + 8px 8px 20px rgba(0, 0, 0, 0.2), + -8px -8px 20px rgba(255, 255, 255, 0.08); + + &:hover { + transform: translateY(-3px); + box-shadow: + 12px 12px 30px rgba(0, 0, 0, 0.25), + -12px -12px 30px rgba(255, 255, 255, 0.12); + } + + /* Style the card body to match the neomorphic theme */ .ant-card-body { padding: 1.25rem; + background-color: transparent; @media only screen and (${media('sm')}) { padding: 1.5rem; @@ -60,7 +88,6 @@ const StyledBaseCard = styled(BaseCard)` } } `; - /** * Styled Modal for a clean “premium” feel: * - Gradient header @@ -82,7 +109,7 @@ const StyledModal = styled(Modal)` border-bottom: none; padding: 24px; } - + .ant-modal-title { color: #fff; font-size: 1.5rem; @@ -297,7 +324,7 @@ const DocumentationCard: React.FC = ({ return ( - {cardTitle} + {cardTitle} theme.boxShadow}; - - @media screen and (${media('md')}) { - max-width: calc(100% - 16px - 170px - 14px); - } - - @media screen and (${media('xl')}) { - max-width: calc(100% - 30px - 170px - 14px); +export const SearchResultsWrapper = styled.div` + max-height: 60vh; + overflow-y: auto; + background: #2a2a2a; + border-radius: 15px; + box-shadow: 8px 8px 16px #1f1f1f, + -8px -8px 16px #353535; + min-width: 400px; + + .ant-list-item { + padding: 12px 16px; + transition: all 0.2s ease; + + &:hover { + background: rgba(255, 255, 255, 0.05); + transform: translateX(8px); + border-radius: 15px; } } - .searchRef { - .ant-popover-content { - overflow-y: auto; - max-height: 252px; - - border: 1px solid ${({ theme }) => theme.primary}; - border-radius: 7px; + a { + color: #e0e0e0 !important; + + &:hover { + color: #ffffff !important; } } `; -export const InputSearch = styled(SearchInput)` - .ant-input-group-addon { - display: none; +export const InputSearch = styled.input` + /* Keep existing styles */ + width: 100%; + padding: 12px 24px; + border: none; + border-radius: 30px; + background: #2a2a2a; + color: #e0e0e0; + box-shadow: inset 5px 5px 10px #1f1f1f, + inset -5px -5px 10px #353535; +`; + +export const Popover = styled(AntdPopover)` + .ant-popover-inner { + background: transparent; + box-shadow: none; + border-radius: 15px; } - .ant-input-group > .ant-input:first-child { - border-radius: 7px; + .ant-popover-content { + margin-top: 10px; } - @media only screen and (${media('md')}) { - .ant-input-group .ant-input-affix-wrapper:not(:last-child) { - border-radius: 3.125rem; - border: 0; - padding: 0.5625rem 1.25rem; - } + .ant-popover-arrow { + display: none; } `; export const Menu = styled.div` - max-height: 50vh; - overflow-y: auto; + background: transparent; `; -export const SearchResultsWrapper = styled.div` - & > div { - .ant-typography { - font-size: ${({ theme }) => theme.fontSizes.xs}; - - &:hover { - text-decoration: none; - } - } - - & .ant-list-header { - font-size: ${({ theme }) => theme.fontSizes.xxs}; - padding-bottom: 0.375rem; - color: ${({ theme }) => theme.primary}; - - @media only screen and (${media('md')}) { - font-size: ${({ theme }) => theme.fontSizes.xs}; - } - } - - & .ant-list-items { - .ant-list-item { - padding-left: 0.5rem; - border-radius: 4px; - - &:hover { - background: #339cfd50; - } - } - } - } -`; - -export const Text = styled(BaseTypography.Text)` - color: ${({ theme }) => theme.textMain}; - - &:hover { - text-decoration: underline; - } -`; +export const Text = styled.span` + color: #e0e0e0; + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`; \ No newline at end of file diff --git a/src/components/dashboard/common/SearchDropdown/SearchDropdown.tsx b/src/components/dashboard/common/SearchDropdown/SearchDropdown.tsx index af00bee3..9cda8d13 100644 --- a/src/components/dashboard/common/SearchDropdown/SearchDropdown.tsx +++ b/src/components/dashboard/common/SearchDropdown/SearchDropdown.tsx @@ -61,21 +61,24 @@ export const SearchDropdown: React.FC = ({ repos }) => { {renderSearchResults} } + overlayStyle={{ + position: 'fixed', + width: dropdownRef.current?.clientWidth // Match input width + }} open={isDropdownOpen} getPopupContainer={() => dropdownRef.current || document.body} showArrow={false} placement="bottomLeft" > -
+
setSearchQuery(event.target.value)} - enterButton={null} - addonAfter={null} + style={{ width: '100%', fontSize: '1.5rem' }} /> -
+
); diff --git a/src/components/dashboard/common/VendorDropdown/VendorDropdown.tsx b/src/components/dashboard/common/VendorDropdown/VendorDropdown.tsx index fe0eff67..ed49cd17 100644 --- a/src/components/dashboard/common/VendorDropdown/VendorDropdown.tsx +++ b/src/components/dashboard/common/VendorDropdown/VendorDropdown.tsx @@ -38,13 +38,44 @@ export const VendorDropdown: React.FC = ({ setSelectedVendo onClick: () => setSelectedVendor(vendor.toLowerCase()), })); + return ( - - - {selectedVendor !== 'all' - ? `${t('dropdown.selectedVendor')}: ${vendorNameFormatted[selectedVendor.toUpperCase()]}` - : t('dropdown.selectVendor')}{' '} - + triggerNode.parentElement!} + > + + + {selectedVendor !== 'all' + ? `${t('dropdown.selectedVendor')}: ${vendorNameFormatted[selectedVendor.toUpperCase()]}` + : t('dropdown.selectVendor')} + + ); diff --git a/src/components/header/Header.styles.ts b/src/components/header/Header.styles.ts index 4975bc82..e09596eb 100644 --- a/src/components/header/Header.styles.ts +++ b/src/components/header/Header.styles.ts @@ -62,3 +62,27 @@ export const ProfileColumn = styled(BaseCol)` padding: ${({ theme }) => `${theme.paddings.md} ${theme.paddings.xl}`}; } `; + +export const HeaderAction = styled.div` + width: 40px; + height: 40px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin: 0 0.5rem; + background: #2a2a2a; + box-shadow: 5px 5px 10px #1f1f1f, + -5px -5px 10px #353535; + transition: all 0.2s ease; + + &:hover { + box-shadow: inset 5px 5px 10px #1f1f1f, + inset -5px -5px 10px #353535; + } + + svg { + color: #e0e0e0; + font-size: 1.25rem; + } +`; \ No newline at end of file diff --git a/src/components/header/layouts/DesktopHeader.tsx b/src/components/header/layouts/DesktopHeader.tsx index 8ce6f084..75429abf 100644 --- a/src/components/header/layouts/DesktopHeader.tsx +++ b/src/components/header/layouts/DesktopHeader.tsx @@ -15,25 +15,30 @@ export const DesktopHeader: React.FC = () => { return ( - - - {pageId} - + + + {pageId} + - + - - - - - + + + - + - + + + diff --git a/src/components/header/layouts/MobileHeader.tsx b/src/components/header/layouts/MobileHeader.tsx index fe8d464b..f401f4cc 100644 --- a/src/components/header/layouts/MobileHeader.tsx +++ b/src/components/header/layouts/MobileHeader.tsx @@ -10,17 +10,20 @@ interface MobileHeaderProps { isSiderOpened: boolean; } +// MobileHeader.tsx export const MobileHeader: React.FC = ({ toggleSider, isSiderOpened }) => { return ( - + + + - + - + diff --git a/src/components/layouts/AuthLayout/AuthLayout.styles.ts b/src/components/layouts/AuthLayout/AuthLayout.styles.ts index 84cb681b..b436c186 100644 --- a/src/components/layouts/AuthLayout/AuthLayout.styles.ts +++ b/src/components/layouts/AuthLayout/AuthLayout.styles.ts @@ -3,7 +3,7 @@ import { LeftOutlined } from '@ant-design/icons'; import { BaseForm } from '@app/components/common/forms/BaseForm/BaseForm'; import { BaseInput as CommonInput } from '@app/components/common/inputs/BaseInput/BaseInput'; import { InputPassword as CommonInputPassword } from '@app/components/common/inputs/InputPassword/InputPassword'; -import loginBackground from '@app/assets/images/login-bg.webp'; +import loginBackground from '@app/assets/images/login.webp'; import { media } from '@app/utils/utils'; import { BaseCheckbox } from '@app/components/common/BaseCheckbox/BaseCheckbox'; import { BaseButton } from '@app/components/common/BaseButton/BaseButton'; @@ -12,14 +12,28 @@ export const Wrapper = styled.div` height: 100vh; width: 100vw; display: flex; + position: relative; `; export const BackgroundWrapper = styled.div` width: 100%; height: 100%; - background: url(${loginBackground}); - background-size: cover; position: relative; + background: #fff; /* White background */ +`; + +/** + * A styled canvas that covers the entire BackgroundWrapper. + * Pointer events are set to none so clicks pass through, + * but you can remove that if needed. + */ +export const Canvas = styled.canvas` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; `; export const LoginWrapper = styled.div` @@ -83,11 +97,11 @@ export const FormCheckbox = styled(BaseCheckbox)` & .ant-checkbox-inner { border-radius: 3px; - transform: scale(1.375); + transform: scale(0.775); } & .ant-checkbox-input { - transform: scale(1.375); + transform: scale(0.775); } `; @@ -154,18 +168,65 @@ export const SubmitButton = styled(BaseButton)` `; export const SocialButton = styled(BaseButton)` - font-size: ${({ theme }) => theme.fontSizes.md}; - font-weight: ${({ theme }) => theme.fontWeights.semibold}; - color: ${({ theme }) => theme.primary}; - border: 1px solid ${({ theme }) => theme.primary}; + /* Basic button styling */ width: 100%; margin-top: 1rem; display: flex; - justify-content: center; align-items: center; - background: transparent; + justify-content: center; + border: none; + outline: none; + cursor: pointer; + border-radius: 16px; /* A bit more rounding for a softer look */ + + /* Font styling */ + font-size: ${({ theme }) => theme.fontSizes.md}; + font-weight: ${({ theme }) => theme.fontWeights.semibold}; + color: #fff; + + /* + * 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 outer shadows */ + box-shadow: + 10px 10px 20px #1f1f1f, /* darker drop shadow (bottom-right) */ + -10px -10px 20px #404040; /* lighter highlight (top-left) */ + + 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; + transform: translateY(-2px); + } + + &:active { + /* + * Inset shadows to simulate a “pressed” look + */ + box-shadow: + inset 10px 10px 20px #1f1f1f, + inset -10px -10px 20px #404040; + 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/AuthLayout/AuthLayout.tsx b/src/components/layouts/AuthLayout/AuthLayout.tsx index 610a6ad6..b6311fc6 100644 --- a/src/components/layouts/AuthLayout/AuthLayout.tsx +++ b/src/components/layouts/AuthLayout/AuthLayout.tsx @@ -1,11 +1,13 @@ import React from 'react'; import { Outlet } from 'react-router-dom'; import * as S from './AuthLayout.styles'; +import { PenifyIntroScene } from './PenifyIntroScene'; const AuthLayout: React.FC = () => { return ( + diff --git a/src/components/layouts/AuthLayout/PenifyIntroScene.tsx b/src/components/layouts/AuthLayout/PenifyIntroScene.tsx new file mode 100644 index 00000000..6dfa89d7 --- /dev/null +++ b/src/components/layouts/AuthLayout/PenifyIntroScene.tsx @@ -0,0 +1,113 @@ +import React, { useEffect, useRef, useState } from 'react'; +import * as THREE from 'three'; + +interface LetterData { + mesh: THREE.Mesh; + initialPos: THREE.Vector3; + velocity: THREE.Vector3; +} + +function createLetterTexture(letter: string): THREE.Texture { + const size = 70; + const canvas = document.createElement('canvas'); + canvas.width = size; + canvas.height = size; + + const ctx = canvas.getContext('2d')!; + ctx.fillStyle = '#000'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.font = 'bold 80px sans-serif'; + ctx.fillText(letter, size / 2, size / 2); + + const texture = new THREE.Texture(canvas); + texture.needsUpdate = true; + return texture; +} + +export const PenifyIntroScene: React.FC = () => { + const containerRef = useRef(null); + const [exploded, setExploded] = useState(false); + + useEffect(() => { + if (!containerRef.current) return; + + // Scene setup + const scene = new THREE.Scene(); + const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 5000); + camera.position.z = 300; + + const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true }); + renderer.setSize(window.innerWidth, window.innerHeight); + containerRef.current.appendChild(renderer.domElement); + + // Create letters + const phrase = 'Penify: Automating Software Documentation'; + const lettersData: LetterData[] = []; + const xSpacing = 15; + const totalWidth = (phrase.length - 1) * xSpacing; + const startX = -totalWidth / 2; + + phrase.split('').forEach((char, i) => { + const texture = createLetterTexture(char); + const material = new THREE.MeshBasicMaterial({ map: texture, transparent: true, side: THREE.DoubleSide }); + const mesh = new THREE.Mesh(new THREE.PlaneGeometry(18, 18), material); + mesh.position.set(startX + i * xSpacing, 0, 0); + scene.add(mesh); + + lettersData.push({ + mesh, + initialPos: mesh.position.clone(), + velocity: new THREE.Vector3() + }); + }); + + // Animation control + setTimeout(() => setExploded(true), 1000); + const clock = new THREE.Clock(); + + const animate = () => { + requestAnimationFrame(animate); + const dt = clock.getDelta(); + + lettersData.forEach((data) => { + if (exploded) { + // Initialize velocity + if (data.velocity.lengthSq() === 0) { + data.velocity.set( + (Math.random() - 0.5) * 1500, + (Math.random() - 0.5) * 5000, + (Math.random() - 0.5) * 500 + ); + } + + // Add central force + const centerForce = data.mesh.position.clone().multiplyScalar(-0.1); + data.velocity.add(centerForce.multiplyScalar(dt)); + + // Update position + data.mesh.position.add(data.velocity.clone().multiplyScalar(dt)); + + // Boundary constraints + const bounds = 250; + ['x', 'y', 'z'].forEach(axis => { + if (Math.abs(data.mesh.position[axis]) > bounds) { + data.velocity[axis] *= -0.8; + data.mesh.position[axis] = Math.sign(data.mesh.position[axis]) * bounds; + } + }); + } + }); + + renderer.render(scene, camera); + }; + animate(); + + return () => { + renderer.dispose(); + containerRef.current?.removeChild(renderer.domElement); + }; + }, [exploded]); + + return
; +}; \ No newline at end of file diff --git a/src/components/layouts/AuthLayout/ThreeBirdsScene.tsx b/src/components/layouts/AuthLayout/ThreeBirdsScene.tsx new file mode 100644 index 00000000..b0012362 --- /dev/null +++ b/src/components/layouts/AuthLayout/ThreeBirdsScene.tsx @@ -0,0 +1,354 @@ +import React, { useEffect, useRef } from 'react'; +import * as THREE from 'three'; + +// -------------- 1) BOID/”Flock” CLASS -------------- +class Flock { + position = new THREE.Vector3(); + velocity = new THREE.Vector3(); + private acceleration = new THREE.Vector3(); + + // You can tweak these for different behaviors + private maxSpeed = 5; + private maxSteerForce = 0.1; + private neighborhoodRadius = 200; + + // Bounds + private width = 500; + private height = 500; + private depth = 400; + private avoidWalls = true; + + setAvoidWalls(value: boolean) { + this.avoidWalls = value; + } + + setWorldSize(width: number, height: number, depth: number) { + this.width = width; + this.height = height; + this.depth = depth; + } + + repulse(target: THREE.Vector3) { + const distance = this.position.distanceTo(target); + if (distance < 150) { + const steer = new THREE.Vector3().subVectors(this.position, target); + steer.multiplyScalar(0.1 / distance); + this.acceleration.add(steer); + } + } + + run(others: Flock[]) { + if (this.avoidWalls) { + this._avoidEdges(); + } + // The original code sometimes partially skips + // alignment/cohesion/separation for performance + if (Math.random() > 0.5) { + this.flock(others); + } + this.move(); + this.checkBounds(); + } + + flock(others: Flock[]) { + // Classic boid: alignment, cohesion, separation + this.acceleration.add(this.alignment(others)); + this.acceleration.add(this.cohesion(others)); + this.acceleration.add(this.separation(others)); + } + + move() { + this.velocity.add(this.acceleration); + const len = this.velocity.length(); + if (len > this.maxSpeed) { + this.velocity.divideScalar(len / this.maxSpeed); + } + this.position.add(this.velocity); + this.acceleration.set(0, 0, 0); + } + + checkBounds() { + // “Wrap” around. You can also do “bounce” if you prefer. + if (this.position.x > this.width) this.position.x = -this.width; + if (this.position.x < -this.width) this.position.x = this.width; + if (this.position.y > this.height) this.position.y = -this.height; + if (this.position.y < -this.height) this.position.y = this.height; + if (this.position.z > this.depth) this.position.z = -this.depth; + if (this.position.z < -this.depth) this.position.z = this.depth; + } + + // For “avoidWalls”, push away from edges + private _avoidEdges() { + const { x, y, z } = this.position; + const steer = new THREE.Vector3(); + const push = 5; + + // left + steer.set(-this.width, y, z); + this.acceleration.add(this.avoid(steer).multiplyScalar(push)); + // right + steer.set(this.width, y, z); + this.acceleration.add(this.avoid(steer).multiplyScalar(push)); + // top + steer.set(x, this.height, z); + this.acceleration.add(this.avoid(steer).multiplyScalar(push)); + // bottom + steer.set(x, -this.height, z); + this.acceleration.add(this.avoid(steer).multiplyScalar(push)); + // front + steer.set(x, y, this.depth); + this.acceleration.add(this.avoid(steer).multiplyScalar(push)); + // back + steer.set(x, y, -this.depth); + this.acceleration.add(this.avoid(steer).multiplyScalar(push)); + } + + avoid(target: THREE.Vector3) { + const steer = new THREE.Vector3().subVectors(this.position, target); + const distSq = this.position.distanceToSquared(target); + if (distSq > 0) { + steer.multiplyScalar(1 / distSq); + } + return steer; + } + + alignment(flocks: Flock[]) { + const velSum = new THREE.Vector3(); + let count = 0; + for (const other of flocks) { + if (Math.random() > 0.6) continue; + const dist = this.position.distanceTo(other.position); + if (dist > 0 && dist <= this.neighborhoodRadius) { + velSum.add(other.velocity); + count++; + } + } + if (count > 0) { + velSum.divideScalar(count); + const l = velSum.length(); + if (l > this.maxSteerForce) { + velSum.divideScalar(l / this.maxSteerForce); + } + } + return velSum; + } + + cohesion(flocks: Flock[]) { + const posSum = new THREE.Vector3(); + let count = 0; + for (const other of flocks) { + if (Math.random() > 0.6) continue; + const dist = this.position.distanceTo(other.position); + if (dist > 0 && dist <= this.neighborhoodRadius) { + posSum.add(other.position); + count++; + } + } + if (count > 0) { + posSum.divideScalar(count); + } + const steer = new THREE.Vector3().subVectors(posSum, this.position); + const l = steer.length(); + if (l > this.maxSteerForce) { + steer.divideScalar(l / this.maxSteerForce); + } + return steer; + } + + separation(flocks: Flock[]) { + const sum = new THREE.Vector3(); + for (const other of flocks) { + if (Math.random() > 0.6) continue; + const dist = this.position.distanceTo(other.position); + if (dist > 0 && dist <= this.neighborhoodRadius) { + const repulse = new THREE.Vector3().subVectors(this.position, other.position); + repulse.normalize(); + repulse.divideScalar(dist); + sum.add(repulse); + } + } + return sum; + } +} + +// -------------- 2) BUILD BIRD BUFFERGEOMETRY -------------- +function createBirdBufferGeometry() { + const positions = new Float32Array([ + // idx=0 + 5, 0, 0, + // idx=1 + -5, -2, 1, + // idx=2 + -5, 0, 0, + // idx=3 + -5, -2, -1, + // idx=4 (wing L) + 0, 2, -6, + // idx=5 (wing R) + 0, 2, 6, + // idx=6 + 2, 0, 0, + // idx=7 + -3, 0, 0, + ]); + + const indices = new Uint16Array([ + 0, 2, 1, + 4, 7, 6, + 5, 6, 7, + ]); + + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute( + 'position', + new THREE.BufferAttribute(positions, 3) + ); + geometry.setIndex(new THREE.BufferAttribute(indices, 1)); + geometry.computeVertexNormals(); + + return geometry; +} + +const WING_L_INDEX = 4; // The left wing vertex index +const WING_R_INDEX = 5; // The right wing vertex index + +// -------------- 3) MAIN COMPONENT -------------- +export const ThreeBirdsScene: React.FC = () => { + const containerRef = useRef(null); + + useEffect(() => { + if (!containerRef.current) return; + + // Create Scene, Camera, Renderer + const scene = new THREE.Scene(); + const camera = new THREE.PerspectiveCamera( + 75, + window.innerWidth / window.innerHeight, + 1, + 5000 + ); + camera.position.z = 450; + + const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true }); + renderer.setSize(window.innerWidth, window.innerHeight); + containerRef.current.appendChild(renderer.domElement); + + // Build our boids & bird meshes + const flocks: Flock[] = []; + const birds: THREE.Mesh[] = []; + + for (let i = 0; i < 200; i++) { + // 1) Flock object + const flock = new Flock(); + flock.position.set( + Math.random() * 400 - 200, + Math.random() * 400 - 200, + Math.random() * 400 - 200 + ); + flock.velocity.set( + Math.random() * 2 - 1, + Math.random() * 2 - 1, + Math.random() * 2 - 1 + ); + flock.setAvoidWalls(true); + flock.setWorldSize(500, 500, 400); + flocks.push(flock); + + // 2) Bird geometry + mesh + const geometry = createBirdBufferGeometry(); + const material = new THREE.MeshBasicMaterial({ + color: Math.random() * 0xffffff, + side: THREE.DoubleSide, + }); + const bird = new THREE.Mesh(geometry, material); + + // Store a "phase" on the mesh for wing flapping + (bird as any).phase = Math.random() * Math.PI * 2; + scene.add(bird); + birds.push(bird); + } + + // Mouse repulsion + const halfX = window.innerWidth / 2; + const halfY = window.innerHeight / 2; + + const onMouseMove = (e: MouseEvent) => { + // Turn 2D mouse coords into a 3D vector around z=0 + const vector = new THREE.Vector3(e.clientX - halfX, -e.clientY + halfY, 0); + flocks.forEach((f) => { + // Repel each boid from the mouse + vector.z = f.position.z; + f.repulse(vector); + }); + }; + window.addEventListener('mousemove', onMouseMove); + + // Resize + const onResize = () => { + camera.aspect = window.innerWidth / window.innerHeight; + camera.updateProjectionMatrix(); + renderer.setSize(window.innerWidth, window.innerHeight); + }; + window.addEventListener('resize', onResize); + + // Animation loop + const animate = () => { + requestAnimationFrame(animate); + + for (let i = 0; i < birds.length; i++) { + const flock = flocks[i]; + const bird = birds[i]; + + // 1) Update boid physics + flock.run(flocks); + + // 2) Sync bird mesh position & rotation + bird.position.copy(flock.position); + const speed = flock.velocity.length(); + bird.rotation.y = Math.atan2(-flock.velocity.z, flock.velocity.x); + bird.rotation.z = Math.asin(flock.velocity.y / speed); + + // 3) Wing flap + (bird as any).phase += (Math.max(0, bird.rotation.z) + 0.1); + const flapY = Math.sin((bird as any).phase) * 5; + + // Update the geometry's “wing” vertices #4 & #5 + const geom = bird.geometry as THREE.BufferGeometry; + const posAttr = geom.getAttribute('position') as THREE.BufferAttribute; + posAttr.setY(WING_L_INDEX, flapY); + posAttr.setY(WING_R_INDEX, flapY); + posAttr.needsUpdate = true; + + // Optional: color shift based on z + const mat = bird.material as THREE.MeshBasicMaterial; + const shade = (500 - bird.position.z) / 1000; // simple grayscale + mat.color.setScalar(shade); + } + + renderer.render(scene, camera); + }; + animate(); + + // Cleanup + return () => { + window.removeEventListener('mousemove', onMouseMove); + window.removeEventListener('resize', onResize); + renderer.dispose(); + }; + }, []); + + return ( +
+ ); +}; diff --git a/src/components/layouts/main/MainHeader/MainHeader.styles.ts b/src/components/layouts/main/MainHeader/MainHeader.styles.ts index 89fb7f09..0d29cc3a 100644 --- a/src/components/layouts/main/MainHeader/MainHeader.styles.ts +++ b/src/components/layouts/main/MainHeader/MainHeader.styles.ts @@ -1,3 +1,4 @@ +// MainHeader.styles.ts import { BaseLayout } from '@app/components/common/BaseLayout/BaseLayout'; import { LAYOUT } from '@app/styles/themes/constants'; import { media } from '@app/utils/utils'; @@ -5,16 +6,70 @@ import styled from 'styled-components'; export const Header = styled(BaseLayout.Header)` line-height: 1.5; - background: ${({ theme }) => theme.layoutHeaderBg}; - padding-top: 1rem; - padding-bottom: 1rem; + background: #2a2a2a; + padding: 1rem 2rem; + box-shadow: 5px 5px 15px #1f1f1f, + -5px -5px 15px #353535; + border-bottom: none; + position: relative; + z-index: 1; @media only screen and (${media('md')}) { - padding: ${({ theme }) => `${theme.paddings.md} ${theme.paddings.xl}`}; height: ${LAYOUT.desktop.headerHeight}; + padding: 0 3rem; } @media only screen and (${media('xl')}) { - padding: 0; + padding: 0 4rem; } `; + +export const TitleColumn = styled.div` + display: flex; + align-items: center; + padding-left: 1rem; + border-radius: 12px; + background: #2a2a2a; + box-shadow: inset 5px 5px 10px #1f1f1f, + inset -5px -5px 10px #353535; +`; + +export const ProfileColumn = styled.div` + display: flex; + justify-content: flex-end; + padding-right: 1rem; +`; + +export const MobileBurger = styled.div<{ isCross: boolean }>` + width: 40px; + height: 40px; + border-radius: 50%; + background: #2a2a2a; + box-shadow: ${({ isCross }) => + isCross + ? 'inset 5px 5px 10px #1f1f1f, inset -5px -5px 10px #353535' + : '5px 5px 10px #1f1f1f, -5px -5px 10px #353535'}; + position: relative; + transition: all 0.2s ease; + + &::before, + &::after { + content: ''; + position: absolute; + background: #e0e0e0; + height: 2px; + width: 24px; + left: 8px; + transition: all 0.3s ease; + } + + &::before { + top: ${({ isCross }) => (isCross ? '19px' : '13px')}; + transform: ${({ isCross }) => (isCross ? 'rotate(45deg)' : 'none')}; + } + + &::after { + top: ${({ isCross }) => (isCross ? '19px' : '25px')}; + transform: ${({ isCross }) => (isCross ? 'rotate(-45deg)' : 'none')}; + } +`; \ No newline at end of file diff --git a/src/components/layouts/main/MainLayout/MainLayout.styles.ts b/src/components/layouts/main/MainLayout/MainLayout.styles.ts index d7ff9b1d..0695b303 100644 --- a/src/components/layouts/main/MainLayout/MainLayout.styles.ts +++ b/src/components/layouts/main/MainLayout/MainLayout.styles.ts @@ -1,17 +1,52 @@ +// MainLayout.styles.ts import styled from 'styled-components'; import { media } from '@app/utils/utils'; import { BaseLayout } from '@app/components/common/BaseLayout/BaseLayout'; export const LayoutMaster = styled(BaseLayout)` height: 100vh; + background: #1a1a1a; + position: relative; + display: flex; `; export const LayoutMain = styled(BaseLayout)` - @media only screen and (${media('md')}) { - margin-left: 80px; - } + flex: 1; + position: relative; + z-index: 2; + background: #1c1c1b; + height: max-content; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + min-height: 100vh; + box-shadow: -8px 0 24px rgba(0, 0, 0, 0.25); + backdrop-filter: blur(12px); + border-left: 1px solid rgba(255, 255, 255, 0.05); - @media only screen and (${media('xl')}) { - margin-left: unset; + &::before { + content: ''; + position: absolute; + top: 0; + left: -2px; + height: 100%; + width: 4px; + background: linear-gradient( + 180deg, + rgba(224, 224, 224, 0.1) 0%, + rgba(64, 64, 64, 0.4) 50%, + rgba(224, 224, 224, 0.1) 100% + ); } + `; + +export const MainContent = styled.div` + // background: rgba(42, 42, 42, 0.6); + border-radius: 16px; + // box-shadow: 8px 8px 24px #1a1a1a, + -8px -8px 24px #2a2a2a; + + @media only screen and (${media('md')}) { + margin: 24px; + padding: 0px 32px 32px 32px; + } +`; \ No newline at end of file diff --git a/src/components/layouts/main/MainLayout/MainLayout.tsx b/src/components/layouts/main/MainLayout/MainLayout.tsx index 88149ba5..4deaeee0 100644 --- a/src/components/layouts/main/MainLayout/MainLayout.tsx +++ b/src/components/layouts/main/MainLayout/MainLayout.tsx @@ -1,7 +1,6 @@ import { useState } from 'react'; import { Header } from '@app/components/header/Header'; import MainSider from '../sider/MainSider/MainSider'; -import MainContent from '../MainContent/MainContent'; import { MainHeader } from '../MainHeader/MainHeader'; import * as S from './MainLayout.styles'; import { Outlet } from 'react-router-dom'; @@ -29,11 +28,11 @@ const MainLayout: React.FC = () => { - +
-
+ ); diff --git a/src/components/layouts/main/MainNav/MainNavs.Styles.ts b/src/components/layouts/main/MainNav/MainNavs.Styles.ts index abf86ff3..db97ec93 100644 --- a/src/components/layouts/main/MainNav/MainNavs.Styles.ts +++ b/src/components/layouts/main/MainNav/MainNavs.Styles.ts @@ -1,25 +1,80 @@ +// MainNavs.Styles.ts import { media } from '@app/utils/utils'; import { Breadcrumb } from 'antd'; import styled from 'styled-components'; export const MainBreadcrumb = styled(Breadcrumb)` - padding: 12px; - border-radius: 8px; - background: ${({ theme }) => theme.background}; + padding: 12px 20px; + border-radius: 15px; + background: #2a2a2a; + box-shadow: 8px 8px 16px #1f1f1f, + -8px -8px 16px #353535; + margin: 16px 24px; + transition: all 0.3s ease; - li:nth-of-type(2) { - text-transform: capitalize; + .ant-breadcrumb-separator { + color: #e0e0e0; + margin: 0 12px; + } + + li { + display: flex; + align-items: center; + + &:nth-of-type(2) { + text-transform: capitalize; + } + + a { + color: #e0e0e0 !important; + padding: 8px 16px; + border-radius: 10px; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: 8px; + + &:hover { + color: #ffffff !important; + box-shadow: inset 4px 4px 8px #1f1f1f, + inset -4px -4px 8px #353535; + } + } + + &:last-child { + color: #ffffff; + padding: 8px 16px; + border-radius: 10px; + background: #2a2a2a; + box-shadow: inset 5px 5px 10px #1f1f1f, + inset -5px -5px 10px #353535; + } } @media only screen and (${media('md')}) { - padding: 14px; + padding: 14px 24px; + margin: 24px 32px; + } + + @media only screen and (${media('xl')}) { + margin: 24px 48px; } `; export const MainBreadcrumbWrapper = styled.div` - padding: ${({ theme }) => `4px ${theme.paddings.sm}`}; + position: relative; + z-index: 2; - @media only screen and (${media('md')}) { - padding: ${({ theme }) => `4px ${theme.paddings.xl}`}; + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, + rgba(255,255,255,0.1) 0%, + rgba(64,64,64,0.3) 50%, + rgba(255,255,255,0.1) 100%); } -`; +`; \ No newline at end of file diff --git a/src/components/layouts/main/MainNav/MainNavs.tsx b/src/components/layouts/main/MainNav/MainNavs.tsx index f9356c7a..4c509e2a 100644 --- a/src/components/layouts/main/MainNav/MainNavs.tsx +++ b/src/components/layouts/main/MainNav/MainNavs.tsx @@ -53,7 +53,25 @@ export const MainNavs = () => { return ( - + { + const isLast = params.index === items.length - 1; + return ( + + {item.icon} + {isLast ? ( + + {item.title} + + ) : item.title} + + ); + }} + /> ); }; diff --git a/src/components/layouts/main/sider/MainSider/MainSider.styles.ts b/src/components/layouts/main/sider/MainSider/MainSider.styles.ts index f57861d8..2e552174 100644 --- a/src/components/layouts/main/sider/MainSider/MainSider.styles.ts +++ b/src/components/layouts/main/sider/MainSider/MainSider.styles.ts @@ -11,7 +11,10 @@ export const Sider = styled(BaseLayout.Sider)` z-index: 5; min-height: 100vh; max-height: 100vh; - color: ${({ theme }) => theme.textSecondary}; + background: #2a2a2a !important; + border-right: none !important; + box-shadow: 10px 10px 20px #1f1f1f, + -10px -10px 20px #353535; @media only screen and (${media('md')}) { right: unset; @@ -20,7 +23,6 @@ export const Sider = styled(BaseLayout.Sider)` &.ant-layout-sider { position: fixed; - background: ${({ theme }) => theme.layoutSiderBg}; @media only screen and (${media('xl')}) { position: unset; @@ -33,24 +35,31 @@ interface Collapse { } export const CollapseButton = styled(BaseButton)` - background: ${({ theme }) => theme.collapseBackground}; - border: 1px solid ${({ theme }) => theme.border}; + background: #2a2a2a; + border: none; + border-radius: 15px; + box-shadow: 5px 5px 10px #1f1f1f, + -5px -5px 10px #353535; + width: 40px; + height: 40px; transition: all 0.2s ease; position: absolute; right: 0.5rem; - color: ${({ theme }) => theme.textSecondary}; + color: #e0e0e0; ${(props) => props.$isCollapsed && css` right: -1rem; + box-shadow: inset 5px 5px 10px #1f1f1f, + inset -5px -5px 10px #353535; `} &.ant-btn:not(:disabled):hover, &.ant-btn:not(:disabled):focus { - color: ${({ theme }) => theme.textSecondary}; - background: ${({ theme }) => theme.primary}; - border: 1px solid ${({ theme }) => theme.border}; + color: #ffffff; + box-shadow: inset 5px 5px 10px #1f1f1f, + inset -5px -5px 10px #353535; } `; @@ -58,6 +67,7 @@ export const SiderContent = styled.div` overflow-y: auto; overflow-x: hidden; max-height: calc(100vh - ${LAYOUT.mobile.headerHeight}); + padding: 16px 8px; @media only screen and (${media('md')}) { max-height: calc(100vh - ${LAYOUT.desktop.headerHeight}); @@ -87,6 +97,8 @@ export const SiderLogoDiv = styled.div` export const BrandSpan = styled.span` margin: 0 1rem; font-weight: ${({ theme }) => theme.fontWeights.bold}; - font-size: ${({ theme }) => theme.fontSizes.lg}; - color: ${({ theme }) => theme.white}; -`; + font-size: 1.5rem; + color: #e0e0e0; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3), + -1px -1px 0 rgba(255, 255, 255, 0.1); +`; \ No newline at end of file diff --git a/src/components/layouts/main/sider/SiderMenu/SiderMenu.styles.ts b/src/components/layouts/main/sider/SiderMenu/SiderMenu.styles.ts index d16a5752..57e361cb 100644 --- a/src/components/layouts/main/sider/SiderMenu/SiderMenu.styles.ts +++ b/src/components/layouts/main/sider/SiderMenu/SiderMenu.styles.ts @@ -2,21 +2,66 @@ import styled from 'styled-components'; import { BaseMenu } from '@app/components/common/BaseMenu/BaseMenu'; export const Menu = styled(BaseMenu)` + background: transparent; + border-right: none; + padding: 0 8px; + a { width: 100%; display: block; + color: #e0e0e0 !important; + border-radius: 12px; + margin: 8px 0; + transition: all 0.3s ease; } - .ant-menu-item.ant-menu-item-selected::after { - border-color: ${({ theme }) => theme.primary}; + .ant-menu-item.ant-menu-item-selected { + background: #2a2a2a !important; + box-shadow: inset 5px 5px 10px #1f1f1f, + inset -5px -5px 10px #353535 !important; + color: #ffffff !important; } .ant-menu-item:hover { - color: ${({ theme }) => theme.textSiderPrimary} !important; + color: #ffffff !important; + background: #2a2a2a !important; + box-shadow: inset 5px 5px 10px #1f1f1f, + inset -5px -5px 10px #353535 !important; } - .ant-menu-item:not(:last-child), + .ant-menu-item, .ant-menu-submenu-title { margin-bottom: 8px; + border-radius: 12px; + box-shadow: 5px 5px 10px #1f1f1f, + -5px -5px 10px #353535; + + &:hover { + box-shadow: inset 5px 5px 10px #1f1f1f, + inset -5px -5px 10px #353535 !important; + } + } + + .ant-menu-item:last-child { + margin-bottom: 8px; + } + + .ant-menu-submenu-title { + color: #e0e0e0 !important; + + &:hover { + color: #ffffff !important; + } + } + + .ant-menu-submenu-arrow { + color: #e0e0e0 !important; + } + + .ant-menu-submenu-open { + .ant-menu-submenu-title { + box-shadow: inset 5px 5px 10px #1f1f1f, + inset -5px -5px 10px #353535 !important; + } } -`; +`; \ No newline at end of file diff --git a/src/components/layouts/main/sider/sidebarNavigation.tsx b/src/components/layouts/main/sider/sidebarNavigation.tsx index d58eb22a..49945eb4 100644 --- a/src/components/layouts/main/sider/sidebarNavigation.tsx +++ b/src/components/layouts/main/sider/sidebarNavigation.tsx @@ -13,14 +13,8 @@ export const sidebarNavigation: SidebarNavigationItem[] = [ { title: 'sidebar.dashboard', key: 'penify-dashboard', + url: '/documentation-generator-dashboard', icon: , - children: [ - { - title: 'sidebar.dashboardMenu', - key: 'documentation-generator-dashboard', - url: '/documentation-generator-dashboard', - }, - ], }, { title: 'sidebar.apiKeys', diff --git a/src/components/profile/ProfileLayout.styles.ts b/src/components/profile/ProfileLayout.styles.ts new file mode 100644 index 00000000..fa442e2b --- /dev/null +++ b/src/components/profile/ProfileLayout.styles.ts @@ -0,0 +1,25 @@ +// ProfileLayout.styles.ts +import styled from 'styled-components'; + +/** + * This container ensures the entire page has a black/gray gradient background, + * giving a consistent "dark" theme behind your neumorphic cards. + */ +export const ProfileLayoutContainer = styled.div` + /* Make the container fill the entire view height to show the gradient fully */ + min-height: 100vh; + border-radius: 16px; /* Optional rounded corners */ + + /* Black/gray gradient background */ + + /* Optional subtle "noise" or pattern can be overlayed if you wish */ + /* background-image: url('/path/to/noise.png'); */ + + /* Since everything is dark, let's ensure the default text is lighter */ + color: #eeeeee; + + /* If you want a default padding around the page content */ + padding: 1rem; + + /* Let children manage their own spacing, or you can do more here */ +`; diff --git a/src/components/profile/ProfileLayout.tsx b/src/components/profile/ProfileLayout.tsx index 61b30ba3..65326183 100644 --- a/src/components/profile/ProfileLayout.tsx +++ b/src/components/profile/ProfileLayout.tsx @@ -1,21 +1,41 @@ -import { useEffect } from 'react'; +// ProfileLayout.tsx +import React, { useEffect } from 'react'; +import { Outlet, useLocation, useNavigate } from 'react-router-dom'; import { LeftOutlined } from '@ant-design/icons'; import { useTranslation } from 'react-i18next'; -import { useNavigate } from 'react-router-dom'; -import { Outlet, useLocation } from 'react-router-dom'; -import styled from 'styled-components'; -import { BaseCard } from '@app/components/common/BaseCard/BaseCard'; -import { BaseButton } from '@app/components/common/BaseButton/BaseButton'; -import { ProfileInfo } from '@app/components/profile/profileCard/ProfileInfo/ProfileInfo'; +import { BaseRow } from '../common/BaseRow/BaseRow'; +import { BaseCol } from '../common/BaseCol/BaseCol'; import { PageTitle } from '@app/components/common/PageTitle/PageTitle'; +import { ProfileInfo } from '@app/components/profile/profileCard/ProfileInfo/ProfileInfo'; import { ProfileNav } from '@app/components/profile/profileCard/ProfileNav/ProfileNav'; import { useResponsive } from '@app/hooks/useResponsive'; import { useAppSelector } from '@app/hooks/reduxHooks'; -import { BaseRow } from '../common/BaseRow/BaseRow'; -import { BaseCol } from '../common/BaseCol/BaseCol'; +import { BaseButton } from '@app/components/common/BaseButton/BaseButton'; +import { BaseCard } from '@app/components/common/BaseCard/BaseCard'; +import { ProfileLayoutContainer } from './ProfileLayout.styles'; // <-- import your new container + +import styled from 'styled-components'; const ProfileCard = styled(BaseCard)` - height: unset; + /* Override or combine with your existing neumorphic styles. */ + background: linear-gradient(45deg, rgba(0, 0, 0, 0.3) 0%, rgba(30, 30, 30, 0.6) 100%); + border-radius: 16px; + border: none; + box-shadow: + 8px 8px 20px rgba(0, 0, 0, 0.4), + -8px -8px 20px rgba(255, 255, 255, 0.04); + + /* Light lift on hover */ + transition: transform 0.3s ease, box-shadow 0.3s ease; + &:hover { + transform: translateY(-3px); + box-shadow: + 12px 12px 30px rgba(0, 0, 0, 0.45), + -12px -12px 30px rgba(255, 255, 255, 0.06); + } + + /* Because the background is dark, ensure internal text is light */ + color: #ffffff; `; const Btn = styled(BaseButton)` @@ -25,6 +45,18 @@ const Btn = styled(BaseButton)` padding: 0; height: unset; color: ${({ theme }) => theme.secondary}; + background: linear-gradient(145deg, #2d2d2d, #1a1a1a); + border: none; + box-shadow: + 4px 4px 8px rgba(0, 0, 0, 0.2), + -4px -4px 8px rgba(255, 255, 255, 0.05); + + &:hover { + box-shadow: + 2px 2px 4px rgba(0, 0, 0, 0.2), + -2px -2px 4px rgba(255, 255, 255, 0.05); + transform: translateY(1px); + } `; const ProfileLayout: React.FC = () => { @@ -41,12 +73,16 @@ const ProfileLayout: React.FC = () => { const isMenuShown = isTabletOrHigher || (mobileOnly && location.pathname !== '/profile'); useEffect(() => { - isTablet && location.pathname === '/profile' && navigate('personal-info'); + if (isTablet && location.pathname === '/profile') { + navigate('personal-info'); + } }, [isTablet, location.pathname, navigate]); return ( - <> + {/* <--- Use the new background container */} {t('profilePage.title')} + + {/* Mobile "Back" button if needed */} {!isTitleShown && ( } type="text" onClick={() => navigate('/profile')}> {t('auth.common.back')} @@ -56,12 +92,12 @@ const ProfileLayout: React.FC = () => { {isTitleShown && ( + {/* Neumorphic card in a dark theme */} - @@ -76,7 +112,7 @@ const ProfileLayout: React.FC = () => { )} - + ); }; diff --git a/src/components/profile/profileCard/ProfileInfo/ProfileInfo.styles.ts b/src/components/profile/profileCard/ProfileInfo/ProfileInfo.styles.ts index 7cf3b6c4..17ac0722 100644 --- a/src/components/profile/profileCard/ProfileInfo/ProfileInfo.styles.ts +++ b/src/components/profile/profileCard/ProfileInfo/ProfileInfo.styles.ts @@ -1,3 +1,4 @@ +/* ProfileInfo.styles.ts */ import styled from 'styled-components'; import { media } from '@app/utils/utils'; import { BaseTypography } from '@app/components/common/BaseTypography/BaseTypography'; @@ -12,20 +13,24 @@ export const Wrapper = styled.div` flex-direction: column; `; +/** + * Container for the avatar circle (with a subtle 3D ring). + */ export const ImgWrapper = styled.div` width: 6.9375rem; margin: 0 auto 1.25rem auto; display: flex; justify-content: center; border-radius: 50%; - background: conic-gradient( - from -35.18deg at 50% 50%, - #006ccf -154.36deg, - #ff5252 24.13deg, - #ffb155 118.76deg, - #006ccf 205.64deg, - #ff5252 384.13deg - ); + + /* + Dark, neumorphic background with subtle highlights/shadows + to give the ring a 3D feeling. + */ + background: linear-gradient(145deg, #2d2d2d, #1a1a1a); + box-shadow: + 8px 8px 16px rgba(0, 0, 0, 0.3), + -8px -8px 16px rgba(255, 255, 255, 0.02); @media only screen and (${media('xl')}) { width: 11.125rem; @@ -33,20 +38,62 @@ export const ImgWrapper = styled.div` } & > span { + /* + The container. We center it with some margin + so the ring effect is visible around it. + */ margin: 5px; width: calc(100% - 10px); height: calc(100% - 10px); @media only screen and (${media('xl')}) { margin: 7px; + width: calc(100% - 14px); + height: calc(100% - 14px); + } + + /* + (Optional) Additional styles if you want the avatar itself + to appear “pushed in.” The .ant-avatar is the container + around the in antd. + */ + .ant-avatar { + background: linear-gradient(#333, #222); + border: none; + display: flex; + align-items: center; + justify-content: center; + box-shadow: + inset 3px 3px 6px rgba(0, 0, 0, 0.5), + inset -3px -3px 6px rgba(255, 255, 255, 0.02); + border-radius: 50%; + } + + /* If you want the actual inside the avatar to have an inset shadow */ + .ant-avatar img { + border-radius: 50%; + box-shadow: + inset 1px 1px 2px rgba(0, 0, 0, 0.5), + inset -1px -1px 2px rgba(255, 255, 255, 0.03); } } `; +/** + * Engraved-looking text: + * - One darker shadow (bottom-right). + * - One lighter shadow (top-left). + */ export const Title = styled(BaseTypography.Text)` font-size: ${({ theme }) => theme.fontSizes.lg}; font-weight: ${({ theme }) => theme.fontWeights.bold}; margin-bottom: 0.5rem; + color: #e2e2e2; /* Slightly lighter than pure white */ + + /* The key: layering “dark + light” shadows to simulate an inset (engraved) look. */ + text-shadow: + 1px 1px 2px rgba(0, 0, 0, 0.7), /* bottom-right darker shadow */ + -1px -1px 2px rgba(255, 255, 255, 0.1); /* top-left subtle highlight */ @media only screen and (${media('xl')}) { font-size: ${({ theme }) => theme.fontSizes.xxl}; @@ -56,6 +103,7 @@ export const Title = styled(BaseTypography.Text)` export const Subtitle = styled(BaseTypography.Text)` margin-bottom: 2rem; + color: #a5a5a5; /* Lighter text color for dark backgrounds */ @media only screen and (${media('xl')}) { font-weight: ${({ theme }) => theme.fontWeights.semibold}; @@ -64,28 +112,31 @@ export const Subtitle = styled(BaseTypography.Text)` } `; +/** + * If you want the fullness bar also to be neumorphic, keep it consistent. + */ export const FullnessWrapper = styled.div` border-radius: 50px; + background: linear-gradient(145deg, #1a1a1a, #2d2d2d); + box-shadow: + inset 3px 3px 6px rgba(0, 0, 0, 0.2), + inset -3px -3px 6px rgba(255, 255, 255, 0.05); + /* Just set a fixed height or place children that define height. */ height: 1.875rem; margin-bottom: 0.625rem; - background-color: rgba(${({ theme }) => theme.rgb.warning}, 0.5); - - @media only screen and (${media('xl')}) { - height: ${({ theme }) => theme.heights.sm}; - margin-bottom: 1rem; - } `; export const FullnessLine = styled.div` display: flex; justify-content: flex-end; - align-items: center; + align-items: center; /* center text if needed */ height: 100%; - padding-right: 0.625rem; border-radius: 50px; width: ${(props) => props.width}%; background: ${({ theme }) => `linear-gradient(90deg, ${theme.warning} 0%, ${theme.error} 100%)`}; - color: ${({ theme }) => theme.textSecondary}; + box-shadow: + inset 3px 3px 6px rgba(0, 0, 0, 0.2), + inset -3px -3px 6px rgba(255, 255, 255, 0.05); @media only screen and (${media('xl')}) { font-size: ${({ theme }) => theme.fontSizes.md}; diff --git a/src/components/profile/profileCard/ProfileInfo/ProfileInfo.tsx b/src/components/profile/profileCard/ProfileInfo/ProfileInfo.tsx index b90bc1c7..e4da8889 100644 --- a/src/components/profile/profileCard/ProfileInfo/ProfileInfo.tsx +++ b/src/components/profile/profileCard/ProfileInfo/ProfileInfo.tsx @@ -61,7 +61,6 @@ export const ProfileInfo: React.FC = ({ profileData }) => { {fullness}% - {t('profilePage.fullness')} ) : null; }; diff --git a/src/components/profile/profileCard/ProfileNav/ProfileNav.styles.ts b/src/components/profile/profileCard/ProfileNav/ProfileNav.styles.ts index 392c833f..a61e1847 100644 --- a/src/components/profile/profileCard/ProfileNav/ProfileNav.styles.ts +++ b/src/components/profile/profileCard/ProfileNav/ProfileNav.styles.ts @@ -36,3 +36,22 @@ export const Btn = styled(BaseButton)` background-color: rgba(${({ theme }) => theme.rgb.primary}, 0.05); } `; + +export const NavItem = styled.div<{ $active: boolean }>` + background: ${({ $active }) => + $active + ? 'linear-gradient(145deg, #2d2d2d, #1a1a1a)' + : 'transparent'}; + box-shadow: ${({ $active }) => + $active + ? '4px 4px 8px rgba(0, 0, 0, 0.2), -4px -4px 8px rgba(255, 255, 255, 0.05)' + : 'none'}; + border-radius: 12px; + transition: all 0.3s ease; + + &:hover { + box-shadow: + inset 3px 3px 6px rgba(0, 0, 0, 0.2), + inset -3px -3px 6px rgba(255, 255, 255, 0.05); + } +`; \ No newline at end of file diff --git a/src/components/profile/profileCard/profileFormNav/nav/PersonalInfo/ConnectAccountItem/ConnectAccountItems.tsx b/src/components/profile/profileCard/profileFormNav/nav/PersonalInfo/ConnectAccountItem/ConnectAccountItems.tsx index 6d5f3a02..decf749b 100644 --- a/src/components/profile/profileCard/profileFormNav/nav/PersonalInfo/ConnectAccountItem/ConnectAccountItems.tsx +++ b/src/components/profile/profileCard/profileFormNav/nav/PersonalInfo/ConnectAccountItem/ConnectAccountItems.tsx @@ -1,38 +1,36 @@ -import { ConfigConnector, getAllConnectors} from '@app/constants/config/connectors'; +import React, { useEffect, useMemo, useState } from 'react'; +import { ConfigConnector, getAllConnectors } from '@app/constants/config/connectors'; import { useResponsive } from '@app/hooks/useResponsive'; -import { useEffect, useMemo, useState } from 'react'; import { ConnectAccountItem } from './accounts/ConnectAccountItem'; -import { SectionWrapper } from './accounts/ConnectAccountItem.styles'; -import { BaseRow } from '@app/components/common/BaseRow/BaseRow'; -import { BaseCol } from '@app/components/common/BaseCol/BaseCol'; + +/** If you want a simple vertical stack, no need for BaseRow/BaseCol. + We'll just do a minimal wrapper. **/ + +import styled from 'styled-components'; + +export const SectionWrapper = styled.div` + display: flex; + flex-wrap: wrap; + gap: 1rem; +`; export const ConnectAccountItems: React.FC = () => { const [connectors, setConnectors] = useState([]); - const { mobileOnly, isTablet: isTabletOrHigher } = useResponsive(); - useEffect(() => { getAllConnectors().then((res) => setConnectors(res)); }, []); - const accountList = useMemo(() => { - return { - mobile: connectors.map((item, index) => ).slice(0, 3), - tablet: connectors.map((item, index) => ( - -
- -
-
- )), - }; - }, [connectors]); - + /** + * If you want a different approach for mobile vs. tablet, + * you can handle that with .map in a row/col layout. + * But here is a simple approach: + */ return ( - {mobileOnly && accountList.mobile} - - {isTabletOrHigher && connectors.length > 0 && accountList.tablet} + {connectors.map((item) => ( + + ))} ); }; diff --git a/src/components/profile/profileCard/profileFormNav/nav/PersonalInfo/ConnectAccountItem/accounts/ConnectAccountItem.styles.ts b/src/components/profile/profileCard/profileFormNav/nav/PersonalInfo/ConnectAccountItem/accounts/ConnectAccountItem.styles.ts index 0307f21e..d27ccb88 100644 --- a/src/components/profile/profileCard/profileFormNav/nav/PersonalInfo/ConnectAccountItem/accounts/ConnectAccountItem.styles.ts +++ b/src/components/profile/profileCard/profileFormNav/nav/PersonalInfo/ConnectAccountItem/accounts/ConnectAccountItem.styles.ts @@ -1,127 +1,82 @@ -import styled from 'styled-components'; -import { BaseButton } from '@app/components/common/BaseButton/BaseButton'; -import { BaseTypography } from '@app/components/common/BaseTypography/BaseTypography'; +import styled, { css } from 'styled-components'; import { BaseCard } from '@app/components/common/BaseCard/BaseCard'; -import { media } from '@app/utils/utils'; - -interface CardInternalProps { - $img: string; -} +import { BaseTypography } from '@app/components/common/BaseTypography/BaseTypography'; +import { BaseAvatar } from '@app/components/common/BaseAvatar/BaseAvatar'; -export const AccountImage = styled.img` - animation: imgOut 0.5s; - width: 100%; - height: 120px; - object-fit: cover; - border-top-left-radius: ${({ theme }) => theme.borderRadius}; - border-top-right-radius: ${({ theme }) => theme.borderRadius}; -`; +/** + * Extra small styling for a dark, neumorphic effect. + * Replace hard-coded #2f2f2f, #1c1c1c, #444444 with your theme colors if desired. + */ -export const AccountInfo = styled.div` - position: relative; - padding: 2rem 1.25rem 1.5rem; -`; +interface CardProps { + $connected?: boolean; +} -export const InfoRow = styled.div` +/** Wrapper around the avatar icon (Google/Github) */ +export const IconWrapper = styled.div` display: flex; align-items: center; - justify-content: space-between; - - &:not(:last-of-type) { - margin-bottom: 0.1rem; - } -`; - -export const Title = styled(BaseTypography.Title)` - transition: all 0.5s ease; - - &.ant-typography { - margin-bottom: 0; - font-size: ${({ theme }) => theme.fontSizes.md}; - } + justify-content: center; + margin-top: 0.2rem; `; -export const IconAvatarWrapper = styled.div` - transition: all 0.5s ease; - position: absolute; - top: -45px; - border-radius: 50%; +/** Minimal “title” text under the icon */ +export const Title = styled(BaseTypography.Text)` + margin-top: 0.25rem; + font-size: ${({ theme }) => theme.fontSizes.xs}; + color: #fff; /* or theme.textLight, etc. */ `; -export const ConnectButton = styled(BaseButton)` - transition: all 0.5s ease; - position: absolute; - top: 40px; - right: 40px; - padding: 10px 14px; - font-size: ${({ theme }) => theme.fontSizes.md}; - border-style: solid; -`; - -export const Card = styled(BaseCard)` +/** The entire card to show the provider (Google, GitHub). */ +export const Card = styled(BaseCard)` + position: relative; + width: 45px; /* small, fixed width */ + height: 45px; /* small, fixed height */ + background: #2f2f2f; + border-radius: 8px; overflow: hidden; - border-width: 3px; - &.ant-card-bordered { - border-color: green; - } - - &:hover { - & { - background: ${(props) => `url(${props.$img})`}; - background-repeat: no-repeat; - background-size: cover; - background-position: center; - position: relative; - } - - ${AccountImage} { - animation: imgIn 0.5s; - animation-fill-mode: forwards; - } - - ${Title} { - color: ${({ theme }) => theme.textSecondary}; - } - - ${IconAvatarWrapper} { - transform: translateY(-70px) scale(1.1); - } + margin: 0.5rem 0; + cursor: ${({ $connected }) => ($connected ? 'default' : 'pointer')}; - ${ConnectButton} { - top: 50%; - left: 50%; - transform: translate(-50%, -50%) scale(1.1); - position: absolute; - } + .ant-card-body { + padding: 0; /* remove default padding */ } - @keyframes imgIn { - 99% { - transform: scale(2); - } - - 100% { - opacity: 0; - } - } - - @keyframes imgOut { - 0% { - transform: scale(2); - } - - 100% { - transform: scale(1); - } + /* Dark neumorphic shadow */ + box-shadow: + 4px 4px 8px #1c1c1c, + -4px -4px 8px #444444; + + /* If connected, use a green border highlight or something subtle */ + ${(p) => + p.$connected && + css` + border: 2px solid #30af5b; /* or theme.success */ + `} + + /* If NOT connected => grayscale the entire card */ + ${(p) => + !p.$connected && + css` + filter: grayscale(1); + `} + + /* Hover effect for non-connected cards only */ + &:hover { + ${({ $connected }) => + !$connected && + css` + box-shadow: + 5px 5px 10px #1c1c1c, + -5px -5px 10px #444444; + filter: grayscale(0.7); + `} } `; -export const SectionWrapper = styled.div` - @media only screen and (${media('md')}) { - display: flex; - flex-direction: column; - width: 100%; - gap: 1.25rem; - margin-bottom: 1.5rem; - } +/** A small, circular avatar for the provider icon */ +export const ProviderAvatar = styled(BaseAvatar)` + width: 40px !important; + height: 40px !important; + border-radius: 50%; `; diff --git a/src/components/profile/profileCard/profileFormNav/nav/PersonalInfo/ConnectAccountItem/accounts/ConnectAccountItem.tsx b/src/components/profile/profileCard/profileFormNav/nav/PersonalInfo/ConnectAccountItem/accounts/ConnectAccountItem.tsx index 61bb8ef8..22868476 100644 --- a/src/components/profile/profileCard/profileFormNav/nav/PersonalInfo/ConnectAccountItem/accounts/ConnectAccountItem.tsx +++ b/src/components/profile/profileCard/profileFormNav/nav/PersonalInfo/ConnectAccountItem/accounts/ConnectAccountItem.tsx @@ -1,7 +1,5 @@ +import React from 'react'; import { useTranslation } from 'react-i18next'; -import * as S from './ConnectAccountItem.styles'; -import { BaseAvatar } from '@app/components/common/BaseAvatar/BaseAvatar'; -import { ConfigConnector } from '@app/constants/config/connectors'; import { useAppSelector } from '@app/hooks/reduxHooks'; import { GITHUB_CONNECTOR_URL, @@ -11,18 +9,20 @@ import { handleOauthSubmit, } from '@app/constants/oauthHandler'; import { useResponsive } from '@app/hooks/useResponsive'; +import { ConfigConnector } from '@app/constants/config/connectors'; +import * as S from './ConnectAccountItem.styles'; export const ConnectAccountItem: React.FC = ({ title, image, Icon, name }) => { const { t } = useTranslation(); - const { isDesktop } = useResponsive(); - const user = useAppSelector((state) => state.user.user); const token = useAppSelector((state) => state.auth.token); + const { isDesktop } = useResponsive(); - const getOAuthDetails = (name: string) => { - if (name === 'google') { + /** Determine if user is connected */ + const getOAuthDetails = (n: string) => { + if (n === 'google') { return { - isConnectionCreated: Boolean(user?.googleLoginId), + isConnectionCreated: !!user?.googleLoginId, providerLoginUrl: GOOGLE_OAUTH_URL, queryParams: { redirect_uri: GOOGLE_CONNECTOR_URL, @@ -31,7 +31,7 @@ export const ConnectAccountItem: React.FC = ({ title, image, Ic }; } else { return { - isConnectionCreated: Boolean(user?.githubLoginId), + isConnectionCreated: !!user?.githubLoginId, providerLoginUrl: GITHUB_OAUTH_URL, queryParams: { redirect_uri: GITHUB_CONNECTOR_URL, @@ -43,23 +43,23 @@ export const ConnectAccountItem: React.FC = ({ title, image, Ic const { isConnectionCreated, providerLoginUrl, queryParams } = getOAuthDetails(name); + const handleClick = () => { + if (!isConnectionCreated) { + handleOauthSubmit(queryParams, providerLoginUrl, isDesktop); + } + }; + return ( - - - {!isConnectionCreated && ( - handleOauthSubmit(queryParams, providerLoginUrl, isDesktop)}> - {t('common.connect')} - - )} - - - } alt="icon" /> - + + {/* We won't show the "image" at all if you want pure icon; + Or if you want, you can do {title} here. + But let's keep it minimal. */} + + } alt={`${name}-icon`} /> + - - {t(title)} - - + {/* Subtle text label, if you want: “Google” or “GitHub” */} + {t(title)} ); }; diff --git a/src/components/profile/profileCard/profileFormNav/nav/PersonalInfo/PersonalInfo.tsx b/src/components/profile/profileCard/profileFormNav/nav/PersonalInfo/PersonalInfo.tsx index fa9f7b48..0b11bf6f 100644 --- a/src/components/profile/profileCard/profileFormNav/nav/PersonalInfo/PersonalInfo.tsx +++ b/src/components/profile/profileCard/profileFormNav/nav/PersonalInfo/PersonalInfo.tsx @@ -52,6 +52,41 @@ const initialPersonalInfoValues: PersonalInfoFormValues = { zipcode: '', }; +// NeumorphicSection.ts +import styled from 'styled-components'; + +export const NeumorphicSection = styled.div` + background: #2f2f2f; + border-radius: 10px; + padding: 1rem; + margin-bottom: 1rem; + box-shadow: + 8px 8px 16px #1c1c1c, /* darker drop shadow */ + -8px -8px 16px #444444; /* lighter "highlight" shadow */ + color: #ffffff; + + /* Add or adjust min-height to align with other sections */ + min-height: 220px; /* or any value that suits your layout */ + + .ant-btn { + background: #2f2f2f; + border: none; + color: #ffffff; + box-shadow: + inset 2px 2px 5px #1c1c1c, + inset -2px -2px 5px #444444; + + &:hover { + box-shadow: + inset 2px 2px 5px #1c1c1c, + inset -2px -2px 5px #444444, + 0 0 2px 1px #00509a; /* or theme’s primary color for focus */ + } + } +`; + + + export const PersonalInfo: React.FC = () => { const user = useAppSelector((state) => state.user.user); @@ -158,14 +193,11 @@ export const PersonalInfo: React.FC = () => { - - - {t('profilePage.heading.connectAccount')} - - - - - + + + {t('profilePage.heading.connectAccount')} + + @@ -190,13 +222,7 @@ export const PersonalInfo: React.FC = () => { - - - {t('profilePage.heading.referral')} - - - - + { } onFinish={onClickVerify} > - - - - - - {isEnabled && ( - - - - )} - - setClickedVerify(false)} - > - setClickedVerify(false)} onFinish={onVerify} /> - ); }; diff --git a/src/components/profile/profileCard/profileFormNav/nav/SecuritySettings/twoFactorAuth/TwoFactorOptions/TwoFactorOptions.tsx b/src/components/profile/profileCard/profileFormNav/nav/SecuritySettings/twoFactorAuth/TwoFactorOptions/TwoFactorOptions.tsx index 11c97b29..970eaa78 100644 --- a/src/components/profile/profileCard/profileFormNav/nav/SecuritySettings/twoFactorAuth/TwoFactorOptions/TwoFactorOptions.tsx +++ b/src/components/profile/profileCard/profileFormNav/nav/SecuritySettings/twoFactorAuth/TwoFactorOptions/TwoFactorOptions.tsx @@ -39,12 +39,7 @@ export const TwoFactorOptions: React.FC = ({ selectedOpti return ( <> setSelectedOption(e.target.value)}> - - - - - ); diff --git a/src/constants/profileNavData.tsx b/src/constants/profileNavData.tsx index aef85032..07dc4456 100644 --- a/src/constants/profileNavData.tsx +++ b/src/constants/profileNavData.tsx @@ -17,13 +17,6 @@ export const profileNavData: ProfileNavItem[] = [ color: 'primary', href: 'personal-info', }, - { - id: 2, - name: 'profilePage.nav.security', - icon: , - color: 'success', - href: 'security-settings', - }, { id: 3, name: 'profilePage.nav.payment', diff --git a/src/hooks/useThemeWatcher.ts b/src/hooks/useThemeWatcher.ts index 2968ceb4..3e89d870 100644 --- a/src/hooks/useThemeWatcher.ts +++ b/src/hooks/useThemeWatcher.ts @@ -1,9 +1,13 @@ import { useEffect, useRef } from 'react'; -import { useAppSelector } from './reduxHooks'; +import { useAppDispatch, useAppSelector } from './reduxHooks'; +import { setTheme } from '@app/store/slices/themeSlice'; export const useThemeWatcher = (): void => { + const dispatch = useAppDispatch(); + dispatch(setTheme('light')); const theme = useAppSelector((state) => state.theme.theme); const root = useRef(document.querySelector(':root')); + useEffect(() => { const html = root.current; diff --git a/src/locales/de/translation.json b/src/locales/de/translation.json index 28ee8a0b..810bb1f5 100644 --- a/src/locales/de/translation.json +++ b/src/locales/de/translation.json @@ -171,7 +171,7 @@ "archUrl": "Click here to go to Architecture Documentation", "archUrlUnavailable": "Architecture Documentation is not available, please generate it.", "docGenLink": "Automated Documentation Generator", - "docstringsDocBtn": "Generate Repository Documentation", + "docstringsDocBtn": "Generate Full Repository Documentation", "featureList": { "list01": { "desc": "Easily generate docs directly from your GitHub repositories.", @@ -345,7 +345,7 @@ "personalInfo": "Personal Info", "planType": "Plan Type", "referral": "Referral", - "referredBy": "Referrer ID (Add the User ID of the user who referred you).", + "referredBy": "Referrer ID (User ID of the user who referred you).", "userID": "User ID", "verified": "Verified", "zipcode": "Zipcode" diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 341ea2cf..f93336a8 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -171,7 +171,7 @@ "archUrl": "Click here to go to Architecture Documentation", "archUrlUnavailable": "Architecture Documentation is not available, please generate it.", "docGenLink": "Automated Documentation Generator", - "docstringsDocBtn": "Generate Repository Documentation", + "docstringsDocBtn": "Generate Full Repository Documentation", "featureList": { "list01": { "desc": "Easily generate docs directly from your GitHub repositories.", @@ -345,7 +345,7 @@ "personalInfo": "Personal Info", "planType": "Plan Type", "referral": "Referral", - "referredBy": "Referrer ID (Add the User ID of the user who referred you).", + "referredBy": "Referrer ID (User ID of the user who referred you).", "userID": "User ID", "verified": "Verified", "zipcode": "Zipcode" @@ -361,7 +361,7 @@ "sidebar": { "apiKeys": "API Keys", "coupons": "Coupons", - "dashboard": "Dashboard", + "dashboard": "Git Repository", "dashboardMenu": "Documentation Generator", "payments": "Payments", "profilePage": "Profile Page", diff --git a/src/styles/themeConfig.ts b/src/styles/themeConfig.ts index aec608f5..30cba6d1 100644 --- a/src/styles/themeConfig.ts +++ b/src/styles/themeConfig.ts @@ -181,7 +181,7 @@ export const getThemeConfig = (theme: DefaultTheme): ThemeConfig => { colorPrimary: theme.primary, colorWhite: theme.background, lineHeight: 1.375, - colorPrimaryBorder: theme.primary1, + colorPrimaryBorder: theme.inputPlaceholder, opacityLoading: 0.4, }, Table: { @@ -189,7 +189,7 @@ export const getThemeConfig = (theme: DefaultTheme): ThemeConfig => { colorBorderSecondary: '#b3cbe1', colorTextHeading: theme.primary, colorFillAlter: `rgba(${theme.rgb.primary}, 0.05)`, - controlItemBgActive: theme.primary1, + controlItemBgActive: theme.inputPlaceholder, colorSplit: 'rgba(0, 0, 0, 0.15)', controlItemBgActiveHover: `rgba(${theme.rgb.primary}, 0.12)`, }, @@ -231,8 +231,8 @@ export const getThemeConfig = (theme: DefaultTheme): ThemeConfig => { colorIconHover: theme.iconHover, colorPrimary: theme.primary, colorPrimaryHover: theme.primary5, - controlItemBgActive: theme.primary1, - controlItemBgHover: theme.itemHoverBg, + controlItemBgActive: theme.inputPlaceholder, + controlItemBgHover: theme.inputPlaceholder, }, Skeleton: { controlHeightXS: 16, @@ -296,7 +296,7 @@ export const getThemeConfig = (theme: DefaultTheme): ThemeConfig => { colorIcon: theme.textLight, colorTextDisabled: theme.textLight, colorPrimary: '#1c68a6', - controlItemBgActive: theme.primary1, + controlItemBgActive: theme.inputPlaceholder, colorTextPlaceholder: theme.inputPlaceholder, fontWeightStrong: theme.fontWeights.medium, controlHeightSM: remToPixels(theme.heights.xxs), diff --git a/src/styles/themes/constants.ts b/src/styles/themes/constants.ts index b4f32c23..dc69f832 100644 --- a/src/styles/themes/constants.ts +++ b/src/styles/themes/constants.ts @@ -7,7 +7,6 @@ export const BASE_COLORS = { black: '#000000', green: '#008000', orange: '#ffb155', - gray: '#808080', lightgray: '#c5d3e0', violet: '#ee82ee', lightgreen: '#89dca0', @@ -15,6 +14,8 @@ export const BASE_COLORS = { blue: '#0000ff', skyblue: '#35a0dc', red: '#ff5252', + gray: '#232322' + } as const satisfies Partial; export const LAYOUT = { diff --git a/src/styles/themes/light/lightTheme.ts b/src/styles/themes/light/lightTheme.ts index 30a54b28..8202fe86 100644 --- a/src/styles/themes/light/lightTheme.ts +++ b/src/styles/themes/light/lightTheme.ts @@ -14,7 +14,7 @@ import { import type { ChartColors, ColorType, ITheme, IndexedPrimaries } from '../types'; const colorTypes = { - primary: '#01509A', + primary: '#e0e0e0', success: '#30AF5B', warning: '#FFB155', error: '#FF5252', @@ -46,7 +46,7 @@ const chartColors = { chartColor5Tint: '#FFC1C1', } as const satisfies ChartColors; -const background = BASE_COLORS.white; +const background = BASE_COLORS.gray; const rgb = Object.fromEntries( Object.entries({ ...colorTypes, ...indexedPrimaries, ...chartColors, background } satisfies ITheme['rgb']).map( @@ -74,16 +74,16 @@ export const lightColorsTheme = { spinnerBase: '#f42f25', scroll: '#c5d3e0', border: '#cce1f4', - textMain: '#404040', + textMain: 'rgba(255, 255, 255, 0.85)', textLight: '#9A9B9F', textSuperLight: '#BEC0C6', - textSecondary: BASE_COLORS.white, + textSecondary: 'rgba(255, 255, 255, 0.65)', textDark: '#404040', textSiderPrimary: '#FFB765', textSiderSecondary: '#ffffff', subText: 'rgba(0, 0, 0, 0.45)', shadow: 'rgba(0, 0, 0, 0.07)', - boxShadow: '0 2px 8px 0 rgba(0, 0, 0, 0.07)', + boxShadow: '4px 4px 8px #1c1c1c,-4px -4px 8px #444444', boxShadowHover: '0 4px 16px 0 rgba(0, 0, 0, 0.2)', ...colorTypes, rgb, @@ -103,15 +103,15 @@ export const lightColorsTheme = { warning: '#FFF4E7', error: '#FFE2E2', }, - heading: '#13264d', + heading: '#f5f5f5', borderBase: '#bec0c6', disabled: 'rgba(0, 0, 0, 0.25)', - disabledBg: '#c5d3e0', + disabledBg: '#4f4f4f', layoutBodyBg: '#f8fbff', layoutHeaderBg: 'transparent', layoutSiderBg: 'linear-gradient(261.31deg, #006ccf -29.57%, #00509a 121.11%)', inputPlaceholder: '#404040', - itemHoverBg: '#f5f5f5', + itemHoverBg: 'black', backgroundColorBase: '#F5F5F5', avatarBg: '#ccc', alertTextColor: BASE_COLORS.white,