@@ -0,0 +1,40 @@ +#!/usr/bin/env sh +# +# If you create/switch to a branch without a prefix (no '/'), +# automatically rename it to "feature/". +# +# Allowed prefixes (when '/' is present): +# fix/, feature/, release/, chore/ +# + +set -eu + +IS_BRANCH_CHECKOUT="${3:-0}" +if [ "$IS_BRANCH_CHECKOUT" != "1" ]; then + exit 0 +fi + +BRANCH="$(git symbolic-ref --quiet --short HEAD 2>/dev/null || true)" +if [ -z "$BRANCH" ]; then + exit 0 +fi + +case "$BRANCH" in + fix/*|feature/*|release/*|chore/*) + exit 0 + ;; +esac + +# No prefix -> default to feature/ +if ! printf "%s" "$BRANCH" | grep -q '/'; then + NEW_BRANCH="feature/$BRANCH" + if git show-ref --verify --quiet "refs/heads/$NEW_BRANCH"; then + exit 0 + fi + git branch -m "$BRANCH" "$NEW_BRANCH" 2>/dev/null || true + exit 0 +fi + +# Has a prefix but it's not allowed: cannot reliably fix here (checkout already happened). +exit 0 + @@ -0,0 +1,35 @@ +#!/usr/bin/env sh +# +# Enforce allowed branch prefixes: +# fix/, feature/, release/, chore/ +# +# If branch has no prefix (no '/'), automatically rename to feature/. +# + +set -eu + +BRANCH="$(git symbolic-ref --quiet --short HEAD 2>/dev/null || true)" +if [ -z "$BRANCH" ]; then + exit 0 +fi + +case "$BRANCH" in + fix/*|feature/*|release/*|chore/*) + exit 0 + ;; +esac + +# No slash => default to feature/ +if ! printf "%s" "$BRANCH" | grep -q '/'; then + NEW_BRANCH="feature/$BRANCH" + if ! git show-ref --verify --quiet "refs/heads/$NEW_BRANCH"; then + git branch -m "$BRANCH" "$NEW_BRANCH" + fi + exit 0 +fi + +echo >&2 "Ошибка: недопустимый префикс ветки '$BRANCH'." +echo >&2 "Разрешены только: fix/, feature/, release/, chore/." +echo >&2 "Либо используйте ветку без префикса — она будет автоматически переименована в feature/<имя>." +exit 1 + @@ -0,0 +1,65 @@ +#!/usr/bin/env sh +# +# Auto-prefix commit messages based on branch name: +# fix/..., feature/..., release/..., chore/... +# -> "refs # " +# + +set -eu + +COMMIT_MSG_FILE="${1:-}" +COMMIT_SOURCE="${2:-}" + +if [ -z "$COMMIT_MSG_FILE" ] || [ ! -f "$COMMIT_MSG_FILE" ]; then + exit 0 +fi + +# Skip for merges/squashes/amends/templates (avoid fighting Git-generated messages). +case "$COMMIT_SOURCE" in + merge|squash|commit|template) + exit 0 + ;; +esac + +BRANCH="$(git symbolic-ref --quiet --short HEAD 2>/dev/null || true)" +TASK_NUMBER="" +if printf "%s" "$BRANCH" | grep -Eq '^(fix|feature|release|chore)/[0-9]+'; then + TASK_NUMBER="$(printf "%s" "$BRANCH" | sed -E 's#^(fix|feature|release|chore)/([0-9]+).*#\2#')" +else + exit 0 +fi + +FIRST_LINE="$(LC_ALL=C sed -n '1p' "$COMMIT_MSG_FILE" || true)" + +# Do not touch special commit subjects. +case "$FIRST_LINE" in + "Merge "*|"fixup! "*|"squash! "*|"revert! "*) + exit 0 + ;; +esac + +PREFIX="refs #${TASK_NUMBER} " + +# Already has the right prefix (or already references the number at start) -> no-op. +case "$FIRST_LINE" in + "$PREFIX"*|"refs #${TASK_NUMBER} "*) + exit 0 + ;; +esac + +# If message already starts with some refs prefix, don't double-prefix. +case "$FIRST_LINE" in + "refs #"*|"refs: #"* ) + exit 0 + ;; +esac + +TMP_FILE="${COMMIT_MSG_FILE}.tmp.$$" +{ + printf "%s%s\n" "$PREFIX" "$FIRST_LINE" + LC_ALL=C sed -n '2,$p' "$COMMIT_MSG_FILE" +} > "$TMP_FILE" + +mv "$TMP_FILE" "$COMMIT_MSG_FILE" +exit 0 + Binary files a/public/voice-clone/add-voice.png and b/public/voice-clone/add-voice.png differ Binary files a/public/voice-clone/my-generations.png and b/public/voice-clone/my-generations.png differ Binary files a/public/voice-clone/step-1.png and b/public/voice-clone/step-1.png differ @@ -4,7 +4,6 @@ import { configureStore } from '@reduxjs/toolkit' import { paramsStore } from '#/app/store/model-parametres-store' import { balanceSlice } from '#/entities/balance' import { userSlice } from '#/entities/user-account' -import { settingsSlice } from '#/entities/user-account/model/settings' import { chatsReducer } from '#/features/chats/chats-slice' import { pendingSlice } from '#/features/pending' import { stepperSlice } from '#/features/register-business' @@ -19,7 +18,6 @@ export const store = configureStore({ user: userSlice.reducer, notification: notificationSlice.reducer, params: paramsStore.reducer, - settings: settingsSlice.reducer, copy: copySlice.reducer, loading: pendingSlice.reducer, chats: chatsReducer, @@ -1,17 +0,0 @@ -import { api } from '#/shared/api' - -import { IUserSetting } from '../model/types' - -export const addUserSettings = async ( - token: string, - setting: Omit -): Promise => { - try { - const { data } = await api.post('/api/users/settings/', setting, { - headers: { Authorization: `Bearer ${token}` }, - }) - return data - } catch (err) { - return null - } -} @@ -2,7 +2,7 @@ import axios, { AxiosResponse } from 'axios' import { getApiUrl } from '#/shared/lib/constants' -import { AccountType } from '../model/types' +import { AccountType } from '../model/account.types' export const getAccountType = async (token: string | null | undefined): Promise => { if (!token) { return 'regular' @@ -1,14 +0,0 @@ -import { api } from '#/shared/api' - -import { IUserSetting } from '../model/types' - -export const getUserSettings = async (token: string): Promise => { - try { - const { data } = await api.get('/api/users/settings/', { - headers: { Authorization: `Bearer ${token}` }, - }) - return data - } catch (err) { - return [] - } -} @@ -1 +0,0 @@ -export * from './settings.routes' \ No newline at end of file @@ -1,15 +0,0 @@ -import { api } from '#/shared/api' -import { Agent } from 'https' -import { IUserSetting } from '../model/types' - -export function getUserSettings() { - return api.get('/api/users/settings/') -} - -export function postUserSettings(option: Omit) { - return api.post('/api/users/settings/', option) -} - -export function updateUserSettings(id: string, value: any) { - return api.put(`/api/users/settings/${id}`, { value }) -} @@ -1,20 +0,0 @@ -import { api } from '#/shared/api' - -import { getUpdatedSettingsLocal } from '../lib/helpers/update-setting-local' -import { IUserSetting, SettingValueType } from '../model/types' - -export const updateUserSettings = async ( - token: string, - id: string, - value: SettingValueType, - settings: IUserSetting[] -) => { - try { - await api.put(`/api/users/settings/${id}`, { value }, { - headers: { Authorization: `Bearer ${token}` }, - }) - return getUpdatedSettingsLocal({ settings, id, value }) - } catch (err) { - return null - } -} @@ -1,23 +0,0 @@ -import { Device } from '#/shared/lib/types/entities' - -import { IUserSetting, SettingType } from '../../model/types' - -interface IProps { - settings: Omit | null - targetType: SettingType - targetDevice: Device -} - -export function isSettingExist({ settings, targetDevice, targetType }: IProps): IUserSetting | null { - if (!settings || settings.length === 0) { - return null - } - - for (let i = 0; i < settings.length; i++) { - if (settings[i].type === targetType && settings[i].device === targetDevice) { - return settings[i] - } - } - - return null -} @@ -1,22 +0,0 @@ -import { IUserSetting, SettingValueType } from '../../model/types' - -interface IProps { - settings: IUserSetting[] - id: string - value: SettingValueType -} - -export function getUpdatedSettingsLocal({ settings, id, value }: IProps): IUserSetting[] { - if (settings.length === 0) { - return settings - } - - return settings.map((setting) => - setting.id === id - ? { - ...setting, - value, - } - : setting - ) -} @@ -0,0 +1,2 @@ +export type AccountType = 'regular' | 'business_host' | 'business_account' + @@ -1,2 +1 @@ -export * from './settings-context' -export * from './use-global-settings' \ No newline at end of file +export {} \ No newline at end of file @@ -1,9 +0,0 @@ -import { createUseContext } from '#/shared' -import { createContext } from 'react' -import { useGlobalSettings } from './use-global-settings' - -export const UserSettingsContext = createContext | null>(null) - -export const useUserSettingsContext = createUseContext(UserSettingsContext) - -export const UserSettingsContextProvider = UserSettingsContext.Provider @@ -1,93 +0,0 @@ -import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' - -import { addUserSettings } from '../api/add-user-settings' -import { getUserSettings } from '../api/get-user-settings' -import { updateUserSettings } from '../api/update-user-settings' -import { getUpdatedSettingsLocal } from '../lib/helpers/update-setting-local' - -import { IUserSetting, SettingValueType } from './types' -import { useLocalStorageSave } from '#/shared/lib/helpers/local-storage-helper' - -interface IAddSettings { - token: string | undefined | null - setting: Omit -} - -export const getUserAccountSettings = createAsyncThunk( - 'users/getSettings', - async (token: string | undefined | null) => { - if (!token) { - return initialState.state - } - return await getUserSettings(token) - } -) - -export const addUserAccountSettings = createAsyncThunk( - 'users/addSettings', - async ({ token, setting }: IAddSettings) => { - if (!token) { - return - } - return await addUserSettings(token, setting) - } -) - -export const updateUserAccountSettings = createAsyncThunk( - 'users/updateSettings', - async ({ - token, - id, - value, - settings, - }: { - token: string | undefined | null - id: string - value: SettingValueType - settings: IUserSetting[] | null - }) => { - if (!token) { - return initialState.state - } - if (!settings) { - return initialState.state - } - return await updateUserSettings(token, id, value, settings) - } -) - -const initialState: { state: IUserSetting[] | null } = { - state: null, -} - -export const settingsSlice = createSlice({ - name: 'balance', - initialState, - reducers: { - setSettings: (state, action) => { - state.state = action.payload - }, - }, - extraReducers: (builder) => { - builder.addCase(getUserAccountSettings.fulfilled, (state, action) => { - state.state = action.payload - localStorage.setItem('global_settings', JSON.stringify(action.payload)) - }) - builder.addCase(addUserAccountSettings.fulfilled, (state, action) => { - if (state.state === null) state.state = [] - if (action.payload) { - state.state = [...state.state, action.payload] - localStorage.setItem('global_settings', JSON.stringify([...state.state, action.payload])) - } - }) - - builder.addCase(updateUserAccountSettings.fulfilled, (state, action) => { - if (action.payload) { - state.state = action.payload - localStorage.setItem('global_settings', JSON.stringify(action.payload)) - } - }) - }, -}) - -export const { setSettings } = settingsSlice.actions @@ -1,16 +0,0 @@ -import { Device } from '#/shared/lib/types/entities' - -export type AccountType = 'regular' | 'business_host' | 'business_account' - -export type SettingType = 'sidemenu' | string - -export type SettingValueType = { - sidemenu_state?: 'opened' | 'closed' -} - -export interface IUserSetting { - id: string - device: Device - type: SettingType - value: SettingValueType | any -} @@ -1,66 +0,0 @@ -import { useLocalStorage } from 'usehooks-ts' -import { IUserSetting } from './types' -import { makePrivateRequest } from '#/shared/api' -import { getUserSettings, postUserSettings, updateUserSettings } from '../api' - -import { getDeviceType } from '#/shared' -import { useCallback, useEffect, useRef, useState } from 'react' -import { useSession } from 'next-auth/react' -import { useShowDataStore } from '#/shared/lib/hooks/use-show-data' - -export function useGlobalSettings() { - const [settings, setSettings] = useLocalStorage('global_settings', []) - - const { showMessage } = useShowDataStore() - - const device = getDeviceType() - - const { data } = useSession() - - const addSettings = makePrivateRequest(async (type: string, value: any) => { - const { status, data } = await postUserSettings({ device, type, value }) - - if (status !== 200) return showMessage('Ошибка создания настроек') - - setSettings((s) => [...s, data]) - }) - - const updateSettings = useCallback( - makePrivateRequest(async (type: string, value: any) => { - const option = settings.find((x) => x.type === type) - - if (!option) return showMessage('Ошибка присвоения настроек') - - const { status } = await updateUserSettings(option.id, value) - - if (status !== 204) return showMessage('Ошибка создания настроек') - - setSettings((s) => [...s.filter((x) => x.type !== type), { ...option, value }]) - }), - [settings, data] - ) - - const fetchUserSettings = makePrivateRequest(async () => { - const { status, data } = await getUserSettings() - - const device = getDeviceType() - - if (status !== 200) return showMessage('Ошибка загрузки данных пользователя') - - setSettings(data.filter((s) => s.device === device)) - }) - - const getOptionValue = (type: string, initial: any) => { - const option = settings.find((o) => o.type === type) - return option ? option.value : initial - } - - return { - settings, - setSettings, - addSettings, - fetchUserSettings, - updateSettings, - getOptionValue, - } -} @@ -73,7 +73,7 @@ export const accountApi = { password_1: string, password_2: string, current_password: string - ): Promise<{ status: number; data: { detail: string } }> { + ): Promise<{status:number,data:{detail:string}}> { const response = await axios.put( getApiUrl() + '/auth/reset-pass', { @@ -8,6 +8,7 @@ interface Props { maxWidth?: string /** By default tooltip stops click propagation (useful inside buttons/menus). Set false when parent needs the click. */ stopClickPropagation?: boolean + wrapperWidth?: string placement?: | 'bottom-end' | 'bottom-start' @@ -36,6 +37,7 @@ export const TooltipCustom: React.FC = ({ className, maxWidth, stopClickPropagation = true, + wrapperWidth, fullWidthTrigger = false, tapToOpenOnMobile = false, }) => { @@ -43,9 +45,10 @@ export const TooltipCustom: React.FC = ({ const tapMode = Boolean(tapToOpenOnMobile && isTouchPrimary) const [tapOpen, setTapOpen] = useState(false) - const triggerSpanStyle: React.CSSProperties | undefined = fullWidthTrigger - ? { display: 'flex', width: '100%', minWidth: 0 } - : undefined + const triggerSpanStyle: React.CSSProperties | undefined = { + ...(fullWidthTrigger ? { display: 'flex', width: '100%', minWidth: 0 } : {}), + ...(wrapperWidth ? { display: 'block', width: wrapperWidth, maxWidth: '100%', minWidth: 0 } : {}), + } const tooltip = ( { useEffect(() => { if (Object.keys(query).length === 0) addQueryParams('setting') }, []) - const changePassword = async () => { - if (!data) return - if (newPassword1 !== newPassword2) { - showMessage('Укажите одинаковые новы пароли!') - return - } - const status = await accountApi.changePassword(data.access, newPassword1, newPassword2, currentPassword) - - if (Number(status) === 200) { - showMessage('Пароль успешно изменён!') - setNewPassword1('') - setNewPassword2('') - setCurrentPassword('') - setTimeout(() => setSuccess(''), 6000) - return - } - - showMessage('К сожалению, произошла ошибка') - } + if (!data) return + if (newPassword1 !== newPassword2) { + showMessage('Укажите одинаковые новые пароли!', 'error') + return + } + + { + const response = await accountApi.changePassword(data.access, newPassword1, newPassword2, currentPassword) + + if (response.status === 200) { + showMessage('Пароль успешно изменён!', 'success') + setNewPassword1('') + setNewPassword2('') + setCurrentPassword('') + setTimeout(() => setSuccess(''), 6000) + return + } + + showMessage(response.data.detail) + } +} function body() { if (type === 'regular') { @@ -231,7 +233,7 @@ const Account: NextPageWithLayout = () => { { headers: { Authorization: `Bearer ${data.access}` } } ) - showMessage('Данные успешно изменены!') + showMessage('Данные успешно изменены!', 'success') dispatch(getAllInfo(data.access)) } catch (e) {} } @@ -275,7 +277,7 @@ const Account: NextPageWithLayout = () => { alignItems='flex-start' spacing={2} sx={{ - width: desktop ? '90%' : '100%', + width: desktop ? (scope === 'referral' ? '97%' : '90%') : '100%', height: '100%', }} > @@ -351,7 +351,7 @@ const ImageModelPage: NextPageWithLayout = () => { ) : ( <> )} - {botParams && botParams.parameters?.length > 0 && ( + {botParams && botParams.parameters?.length > 0 && Object.keys(filteredParams).length > 0 && ( { )} - {botParams && botParams.parameters?.length > 0 ? ( + {botParams && botParams.parameters?.length > 0 && Object.keys(filteredParams).length > 0 ? ( setOpenFiltersMobile(false)} reset={resetParams} /> @@ -433,7 +433,7 @@ const ImageModelPage: NextPageWithLayout = () => { ) : ( <> )} - {botParams && botParams.parameters?.length > 0 ? ( + {botParams && botParams.parameters?.length > 0 && Object.keys(filteredParams).length > 0 ? ( <> = ({ email }) => { const dispatch = useDispatch() - const { push } = useRouter() + const { push, replace } = useRouter() + const { status } = useSession() + const { showMessage } = useShowDataStore() useEffect(() => { + if (status === 'loading') return + + if (status === 'authenticated') { + showMessage(REFERRAL_BLOCKED_MESSAGE, 'error') + replace('/') + return + } + localStorage.setItem('referral', email) dispatch(addReferral(email)) push('/register') - }, []) + }, [status, email, dispatch, push, replace, showMessage]) return ( @@ -79,7 +79,7 @@ export const PersonsList = ({ }) ) setCurrentPerson(null) - showMessage(`Лимит пользователя ${email} успешно изменён!`, 'success') + showMessage(`Данные сотрудника ${email} изменены!`, 'success') } const handleOpenResendModal = async (email: string) => { @@ -96,7 +96,7 @@ export const SecurityList = ({ }) ) setCurrentPerson(null) - showMessage(`Лимит пользователя ${email} успешно изменён!`, 'success') + showMessage(`Данные сотрудника ${email} изменены!`, 'success') } useEffect(() => { @@ -181,9 +181,7 @@ export const SecurityList = ({ className={styles.refreshStatusButton} onClick={() => { passResendModal.setState(true, { - callback: () => { - console.log('Смена пароля') - }, + email:person.email, }) }} > @@ -202,9 +200,7 @@ export const SecurityList = ({