mrged-testing

This commit is contained in:
tonyrewin 2022-12-02 09:28:05 +03:00
commit c5906a2762
25 changed files with 747 additions and 156 deletions

View File

@ -56,6 +56,7 @@
"@solid-devtools/logger": "^0.5.0",
"@solid-primitives/memo": "^1.1.2",
"@solid-primitives/storage": "^1.3.3",
"@solid-primitives/upload": "^0.0.105",
"@types/express": "^4.17.14",
"@types/node": "^18.11.9",
"@types/uuid": "^8.3.4",

3
public/icons/apple.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.9159 0C13.0948 1.21486 12.6003 2.40503 11.948 3.24708C11.2501 4.15029 10.0472 4.84886 8.88168 4.8124C8.66892 3.64929 9.21368 2.45089 9.87651 1.6453C10.6036 0.756201 11.8498 0.0740913 12.9159 0ZM16.4177 17.0985C17.0185 16.1776 17.243 15.7131 17.7094 14.6735C14.3169 13.3833 13.7733 8.56035 17.1308 6.70925C16.1067 5.425 14.6676 4.68056 13.3093 4.68056C12.3305 4.68056 11.6599 4.93598 11.0502 5.16818C10.5421 5.36169 10.0764 5.53907 9.50996 5.53907C8.89784 5.53907 8.35578 5.34472 7.78819 5.14121C7.1645 4.91758 6.50998 4.68291 5.69781 4.68291C4.17342 4.68291 2.55083 5.61434 1.5221 7.20672C0.0760363 9.44945 0.322698 13.6656 2.66774 17.2573C3.50592 18.5427 4.62583 19.9869 6.0906 19.9998C6.69839 20.0058 7.10283 19.8244 7.5405 19.6281C8.04146 19.4035 8.58593 19.1592 9.52867 19.1542C10.477 19.1485 11.0128 19.3957 11.5071 19.6237C11.9336 19.8204 12.3291 20.0028 12.9317 19.9963C14.3976 19.9845 15.5795 18.3839 16.4177 17.0985Z" fill="#0B0B0A"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,3 @@
<svg width="24" height="18" viewBox="0 0 24 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.65441 0.231661L9.68495 5.65813C9.7181 5.6779 9.75068 5.69975 9.78214 5.72348C9.82187 5.75305 9.85898 5.78488 9.89344 5.81859L22.6933 15.6986C23.1773 16.0711 23.266 16.7642 22.8933 17.2482C22.6752 17.5322 22.3479 17.6776 22.0184 17.6776C21.7821 17.6776 21.5434 17.6027 21.3436 17.4482L18.8218 15.502C16.7955 16.7934 14.453 17.4731 12 17.4731C6.69216 17.4731 2.01349 14.2511 0.0752888 9.26566C-0.0247164 9.01122 -0.0247164 8.72721 0.0730288 8.47259C0.783219 6.61144 1.93866 4.91947 3.39091 3.59094L1.30455 1.9807C0.820536 1.60798 0.732016 0.914929 1.10473 0.431054C1.47726 -0.0552304 2.17484 -0.141678 2.65437 0.231044L2.65441 0.231661ZM2.29996 8.86387C3.9927 12.7677 7.75343 15.2647 12.0001 15.2647C13.7543 15.2647 15.4355 14.8474 16.9372 14.0475L14.9695 12.529C14.1267 13.1978 13.0877 13.5675 12 13.5675C9.3778 13.5675 7.24641 11.4589 7.24641 8.86623C7.24641 8.18391 7.392 7.51721 7.67224 6.8966L5.1736 4.96766C3.94473 6.0071 2.94617 7.35878 2.29982 8.86377L2.29996 8.86387ZM12.0025 0.209C17.3195 0.209 22.0001 3.45157 23.9272 8.47092C24.025 8.72781 24.025 9.01184 23.9227 9.26853C23.5252 10.291 22.9571 11.3067 22.2868 12.1998C22.0708 12.4906 21.7369 12.6427 21.4028 12.6427C21.1711 12.6427 20.9393 12.5701 20.7415 12.4224C20.253 12.0567 20.1532 11.3636 20.5212 10.8773C20.9869 10.2569 21.3892 9.56631 21.7005 8.86171C20.0145 4.93305 16.2538 2.41745 12.0028 2.41745C10.9849 2.41745 10.0032 2.55832 9.08288 2.83329C8.49452 3.01278 7.88317 2.6766 7.7082 2.09258C7.53324 1.50857 7.86509 0.892876 8.44891 0.717908C9.57365 0.381539 10.7713 0.208817 12.0028 0.208817L12.0025 0.209ZM9.45528 8.86628C9.45528 10.241 10.596 11.3589 12.0003 11.3589C12.3922 11.3589 12.7726 11.2687 13.1182 11.0997L9.51502 8.31838C9.47754 8.48806 9.45532 8.67094 9.45532 8.86623L9.45528 8.86628ZM12.4319 4.11948C14.1339 4.31497 15.5948 5.35327 16.3357 6.89612C16.5992 7.44604 16.3674 8.1073 15.8176 8.37078C15.663 8.44593 15.5018 8.48002 15.3404 8.48002C14.9291 8.48002 14.536 8.25044 14.3429 7.85285C13.9339 6.99836 13.125 6.42355 12.182 6.31454C11.5754 6.24636 11.1413 5.69644 11.2094 5.09208C11.2799 4.48526 11.8479 4.05587 12.4319 4.11951L12.4319 4.11948Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,3 @@
<svg width="24" height="18" viewBox="0 0 24 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.9998 0C17.3178 0 21.9994 3.24315 23.927 8.26336C24.0247 8.52025 24.0247 8.80427 23.9247 9.06115C21.9884 14.0474 17.3093 17.27 12 17.27C6.6907 17.27 2.01155 14.0474 0.0752887 9.06115C-0.0247163 8.80426 -0.0247163 8.52024 0.0730287 8.26336C2.00028 3.24334 6.68169 0 12.0002 0H11.9998ZM11.9998 2.21123C7.74537 2.21123 3.9819 4.72722 2.29779 8.65886C3.99092 12.561 7.75463 15.0588 11.9998 15.0588C16.2451 15.0588 20.0086 12.561 21.7019 8.65886C20.018 4.72717 16.2547 2.21123 11.9998 2.21123V2.21123ZM11.9998 3.90662C14.6224 3.90662 16.7542 6.03839 16.7542 8.66112C16.7542 11.2564 14.6201 13.3631 11.9998 13.3631C9.37958 13.3631 7.24554 11.2541 7.24554 8.66112C7.24554 6.03839 9.37731 3.90662 11.9998 3.90662V3.90662ZM11.9998 6.11804C10.5977 6.11804 9.4545 7.25878 9.4545 8.66339C9.4545 10.0383 10.5954 11.1564 11.9998 11.1564C13.4043 11.1564 14.5452 10.0383 14.5452 8.66339C14.5452 7.25878 13.402 6.11804 11.9998 6.11804V6.11804Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,10 @@
.navigationHeader {
@include font-size(1.8rem);
font-weight: bold;
margin-top: 1.1em !important;
}
.navigation {
@include font-size(1.4rem);
}

View File

@ -0,0 +1,21 @@
import styles from './ProfileSettingsNavigation.module.scss'
import { clsx } from 'clsx'
export default () => {
return (
<>
<h4 class={styles.navigationHeader}>Настройки</h4>
<ul class={clsx(styles.navigation, 'nodash')}>
<li>
<a href="/profile/settings">Профиль</a>
</li>
<li>
<a href="/profile/subscriptions">Подписки</a>
</li>
<li>
<a href="/profile/security">Вход и&nbsp;безопасность</a>
</li>
</ul>
</>
)
}

View File

@ -0,0 +1,135 @@
import { PageWrap } from '../../_shared/PageWrap'
import type { PageProps } from '../../types'
import styles from './Settings.module.scss'
import { Icon } from '../../_shared/Icon'
import { clsx } from 'clsx'
import ProfileSettingsNavigation from '../../Discours/ProfileSettingsNavigation'
export const ProfileSecurityPage = (props: PageProps) => {
return (
<PageWrap>
<div class="wide-container">
<div class="shift-content">
<div class="left-col">
<div class={clsx('left-navigation', styles.leftNavigation)}>
<ProfileSettingsNavigation />
</div>
</div>
<div class="row">
<div class="col-md-10 col-lg-9 col-xl-8">
<h1>Вход и&nbsp;безопасность</h1>
<p class="description">Настройки аккаунта, почты, пароля и&nbsp;способов входа.</p>
<form>
<h4>Почта</h4>
<div class="pretty-form__item">
<input type="text" name="email" id="email" placeholder="Почта" />
<label for="email">Почта</label>
</div>
<h4>Изменить пароль</h4>
<h5>Текущий пароль</h5>
<div class="pretty-form__item">
<input
type="text"
name="password-current"
id="password-current"
class={clsx(styles.passwordInput, 'nolabel')}
/>
<button type="button" class={styles.passwordToggleControl}>
<Icon name="password-hide" />
</button>
</div>
<h5>Новый пароль</h5>
<div class="pretty-form__item">
<input
type="password"
name="password-new"
id="password-new"
class={clsx(styles.passwordInput, 'nolabel')}
/>
<button type="button" class={styles.passwordToggleControl}>
<Icon name="password-open" />
</button>
</div>
<h5>Подтвердите новый пароль</h5>
<div class="pretty-form__item">
<input
type="password"
name="password-new-confirm"
id="password-new-confirm"
class={clsx(styles.passwordInput, 'nolabel')}
/>
<button type="button" class={styles.passwordToggleControl}>
<Icon name="password-open" />
</button>
</div>
<h4>Социальные сети</h4>
<h5>Google</h5>
<div class="pretty-form__item">
<p>
<button class={clsx('button button--light', styles.socialButton)} type="button">
<Icon name="google" class={styles.icon} />
Привязать
</button>
</p>
</div>
<h5>VK</h5>
<div class="pretty-form__item">
<p>
<button class={clsx(styles.socialButton, 'button button--light')} type="button">
<Icon name="vk" class={styles.icon} />
Привязать
</button>
</p>
</div>
<h5>Facebook</h5>
<div class="pretty-form__item">
<p>
<button class={clsx(styles.socialButton, 'button button--light')} type="button">
<Icon name="facebook" class={styles.icon} />
Привязать
</button>
</p>
</div>
<h5>Apple</h5>
<div class="pretty-form__item">
<p>
<button
class={clsx(
styles.socialButton,
styles.socialButtonApple,
'button' + ' button--light'
)}
type="button"
>
<Icon name="apple" class={styles.icon} />
Привязать
</button>
</p>
</div>
<br />
<p>
<button class="button button--submit" type="submit">
Сохранить настройки
</button>
</p>
</form>
</div>
</div>
</div>
</div>
</PageWrap>
)
}
// for lazy loading
export default ProfileSecurityPage

View File

@ -1,122 +1,182 @@
import { PageWrap } from '../../_shared/PageWrap'
import { t } from '../../../utils/intl'
import type { PageProps } from '../../types'
import styles from './Settings.module.scss'
import { Icon } from '../../_shared/Icon'
import ProfileSettingsNavigation from '../../Discours/ProfileSettingsNavigation'
import { For, createSignal, Show } from 'solid-js'
import { clsx } from 'clsx'
import styles from './Settings.module.scss'
import { useProfileForm } from '../../../context/profile'
import { createFileUploader } from '@solid-primitives/upload'
export const ProfileSettingsPage = (props: PageProps) => {
const [addLinkForm, setAddLinkForm] = createSignal<boolean>(false)
const { form, updateFormField, submit } = useProfileForm()
const handleChangeSocial = (value) => {
updateFormField('links', value)
setAddLinkForm(false)
}
const handleSubmit = (event: Event): void => {
event.preventDefault()
submit(form)
}
const { selectFiles: selectFilesAsync } = createFileUploader({ accept: 'image/*' })
const handleUpload = () => {
selectFilesAsync(async ([{ source, name, size, file }]) => {
try {
console.log({ source, name, size, file })
// DO UPLOAD STUFF HERE AND RETURN URL
} catch (error) {
console.log(error)
}
})
}
return (
<PageWrap>
<div class="wide-container">
<div class="shift-content">
<div class="left-col">
<div class="left-navigation">WIP</div>
</div>
<div class="row">
<div class="col-md-10 col-lg-9 col-xl-8">
<h1>Настройки профиля</h1>
<p class="description">Здесь можно настроить свой профиль так, как вы хотите.</p>
<form>
<h4>Аватар</h4>
<div class="pretty-form__item">
<div class={styles.avatarContainer}>
<img class={styles.avatar} />
<input
type="file"
name="avatar"
class={styles.avatarInput}
accept="image/jpeg,image/png,image/gif,image/webp"
/>
</div>
</div>
<h4>Имя</h4>
<p class="description">
Ваше имя появится на&nbsp;странице вашего профиля и&nbsp;как ваша подпись
в&nbsp;публикациях, комментариях и&nbsp;откликах
</p>
<div class="pretty-form__item">
<input type="text" name="username" id="username" placeholder="Имя" />
<label for="username">Имя</label>
</div>
<h4>Адрес на&nbsp;Дискурсе</h4>
<div class="pretty-form__item">
<div class={styles.discoursName}>
<label for="user-address">https://discours.io/user/</label>
<div class={styles.discoursNameField}>
<input type="text" name="user-address" id="user-address" class="nolabel" />
<p class="form-message form-message--error">
Увы, этот адрес уже занят, выберите другой
</p>
<Show when={form}>
<div class="wide-container">
<div class="shift-content">
<div class="left-col">
<div class={clsx('left-navigation', styles.leftNavigation)}>
<ProfileSettingsNavigation />
</div>
</div>
<div class="row">
<div class="col-md-10 col-lg-9 col-xl-8">
<h1>{t('Profile settings')}</h1>
<p class="description">{t('Here you can customize your profile the way you want.')}</p>
<form onSubmit={handleSubmit}>
<h4>{t('Userpic')}</h4>
<div class="pretty-form__item">
<div class={styles.avatarContainer}>
<img class={styles.avatar} src={form.userpic} alt={form.name} />
<button type="button" class={styles.avatarInput} onClick={handleUpload} />
</div>
</div>
</div>
<h4>Представление</h4>
<div class="pretty-form__item">
<textarea name="presentation" id="presentation" placeholder="Представление" />
<label for="presentation">Представление</label>
</div>
<h4>О себе</h4>
<div class="pretty-form__item">
<textarea name="about" id="about" placeholder="О себе" />
<label for="about">О себе</label>
</div>
<h4>Чем могу помочь/навыки</h4>
<div class="pretty-form__item">
<input type="text" name="skills" id="skills" />
</div>
<h4>Откуда</h4>
<div class="pretty-form__item">
<input type="text" name="location" id="location" placeholder="Откуда" />
<label for="location">Откуда</label>
</div>
<h4>Дата рождения</h4>
<div class="pretty-form__item">
<input
type="date"
name="birthdate"
id="birthdate"
placeholder="Дата рождения"
class="nolabel"
/>
</div>
<div class={clsx(styles.multipleControls, 'pretty-form__item')}>
<div class={styles.multipleControlsHeader}>
<h4>Социальные сети</h4>
<button class="button">+</button>
<h4>{t('Name')}</h4>
<p class="description">
{t(
'Your name will appear on your profile page and as your signature in publications, comments and responses.'
)}
</p>
<div class="pretty-form__item">
<input
type="text"
name="username"
id="username"
placeholder={t('Name')}
onChange={(event) => updateFormField('name', event.currentTarget.value)}
value={form.name}
/>
<label for="username">Имя</label>
</div>
<div class={styles.multipleControlsItem}>
<input type="text" name="social1" class="nolabel" />
<button>
<Icon name="remove" class={styles.icon} />
{/*Не готов бекенд*/}
{/*<h4>{t('Address on Discourse')}</h4>*/}
{/*<div class="pretty-form__item">*/}
{/* <div class={styles.discoursName}>*/}
{/* <label for="user-address">https://discours.io/user/</label>*/}
{/* <div class={styles.discoursNameField}>*/}
{/* <input*/}
{/* type="text"*/}
{/* name="user-address"*/}
{/* id="user-address"*/}
{/* onChange={(event) => updateFormField('slug', event.currentTarget.value)}*/}
{/* value={form.slug}*/}
{/* class="nolabel"*/}
{/* />*/}
{/* <p class="form-message form-message--error">*/}
{/* {t('Sorry, this address is already taken, please choose another one.')}*/}
{/* </p>*/}
{/* </div>*/}
{/* </div>*/}
{/*</div>*/}
{/*Нет реализации полей на бэке*/}
{/*<h4>{t('Introduce')}</h4>*/}
{/*<div class="pretty-form__item">*/}
{/* <textarea name="presentation" id="presentation" placeholder={t('Introduce')} />*/}
{/* <label for="presentation">{t('Introduce')}</label>*/}
{/*</div>*/}
<h4>{t('About myself')}</h4>
<div class="pretty-form__item">
<textarea
name="about"
id="about"
placeholder={t('About myself')}
value={form.bio}
onChange={(event) => updateFormField('bio', event.currentTarget.value)}
/>
<label for="about">{t('About myself')}</label>
</div>
{/*Нет реализации полей на бэке*/}
{/*<h4>{t('How can I help/skills')}</h4>*/}
{/*<div class="pretty-form__item">*/}
{/* <input type="text" name="skills" id="skills" />*/}
{/*</div>*/}
{/*<h4>{t('Where')}</h4>*/}
{/*<div class="pretty-form__item">*/}
{/* <input type="text" name="location" id="location" placeholder="Откуда" />*/}
{/* <label for="location">{t('Where')}</label>*/}
{/*</div>*/}
{/*<h4>{t('Date of Birth')}</h4>*/}
{/*<div class="pretty-form__item">*/}
{/* <input*/}
{/* type="date"*/}
{/* name="birthdate"*/}
{/* id="birthdate"*/}
{/* placeholder="Дата рождения"*/}
{/* class="nolabel"*/}
{/* />*/}
{/*</div>*/}
<div class={clsx(styles.multipleControls, 'pretty-form__item')}>
<div class={styles.multipleControlsHeader}>
<h4>{t('Social networks')}</h4>
<button type="button" class="button" onClick={() => setAddLinkForm(!addLinkForm())}>
+
</button>
</div>
<Show when={addLinkForm()}>
<div class={styles.multipleControlsItem}>
<input
autofocus={true}
type="text"
name="link"
class="nolabel"
onChange={(event) => handleChangeSocial(event.currentTarget.value)}
/>
</div>
</Show>
<For each={form.links}>
{(link) => (
<div class={styles.multipleControlsItem}>
<input type="text" value={link} readonly={true} name="link" class="nolabel" />
<button type="button" onClick={() => updateFormField('links', link, true)}>
<Icon name="remove" class={styles.icon} />
</button>
</div>
)}
</For>
</div>
<br />
<p>
<button type="submit" class="button button--submit">
{t('Save settings')}
</button>
</div>
<div class={styles.multipleControlsItem}>
<input type="text" name="social1" class="nolabel" />
<button>
<Icon name="remove" class={styles.icon} />
</button>
</div>
</div>
<br />
<p>
<button class="button button--submit">Сохранить настройки</button>
</p>
</form>
</p>
</form>
</div>
</div>
</div>
<pre>{JSON.stringify(form, null, 2)}</pre>
</div>
</div>
</Show>
</PageWrap>
)
}

View File

@ -0,0 +1,137 @@
import { PageWrap } from '../../_shared/PageWrap'
import type { PageProps } from '../../types'
import styles from './Settings.module.scss'
import stylesSettings from '../../../styles/FeedSettings.module.scss'
import { clsx } from 'clsx'
import ProfileSettingsNavigation from '../../Discours/ProfileSettingsNavigation'
import { SearchField } from '../../_shared/SearchField'
export const ProfileSubscriptionsPage = (props: PageProps) => {
return (
<PageWrap>
<div class="wide-container">
<div class="shift-content">
<div class="left-col">
<div class={clsx('left-navigation', styles.leftNavigation)}>
<ProfileSettingsNavigation />
</div>
</div>
<div class="row">
<div class="col-md-10 col-lg-9 col-xl-8">
<h1>Подписки</h1>
<p class="description">Здесь можно управлять всеми своими подписками на&nbsp;сайте.</p>
<form>
<ul class="view-switcher">
<li class="selected">
<a href="#">Все</a>
</li>
<li>
<a href="#">Авторы</a>
</li>
<li>
<a href="#">Темы</a>
</li>
<li>
<a href="#">Сообщества</a>
</li>
<li>
<a href="#">Коллекции</a>
</li>
</ul>
<div class={clsx('pretty-form__item', styles.searchContainer)}>
<SearchField onChange={() => console.log('nothing')} class={styles.searchField} />
</div>
<div class={clsx(stylesSettings.settingsList, styles.topicsList)}>
<div class={stylesSettings.settingsListRow}>
<div class={clsx(stylesSettings.settingsListCell, styles.topicsListItem)}>
<input type="checkbox" name="checkbox1" id="checkbox1" />
<label for="checkbox1" />
</div>
<label for="checkbox1" class={styles.settingsListCell}>
Культура
</label>
</div>
<div class={stylesSettings.settingsListRow}>
<div class={clsx(stylesSettings.settingsListCell, styles.topicsListItem)}>
<input type="checkbox" name="checkbox2" id="checkbox2" />
<label for="checkbox2" />
</div>
<label for="checkbox2" class={styles.settingsListCell}>
Eto_ya sam
</label>
</div>
<div class={stylesSettings.settingsListRow}>
<div class={clsx(stylesSettings.settingsListCell, styles.topicsListItem)}>
<input type="checkbox" name="checkbox3" id="checkbox3" />
<label for="checkbox3" />
</div>
<label for="checkbox3" class={styles.settingsListCell}>
Технопарк
</label>
</div>
<div class={stylesSettings.settingsListRow}>
<div class={clsx(stylesSettings.settingsListCell, styles.topicsListItem)}>
<input type="checkbox" name="checkbox4" id="checkbox4" />
<label for="checkbox4" />
</div>
<label for="checkbox4" class={styles.settingsListCell}>
Лучшее
</label>
</div>
<div class={stylesSettings.settingsListRow}>
<div class={clsx(stylesSettings.settingsListCell, styles.topicsListItem)}>
<input type="checkbox" name="checkbox5" id="checkbox5" />
<label for="checkbox5" />
</div>
<label for="checkbox5" class={styles.settingsListCell}>
Реклама
</label>
</div>
<div class={stylesSettings.settingsListRow}>
<div class={clsx(stylesSettings.settingsListCell, styles.topicsListItem)}>
<input type="checkbox" name="checkbox6" id="checkbox6" />
<label for="checkbox6" />
</div>
<label for="checkbox6" class={styles.settingsListCell}>
Искусство
</label>
</div>
<div class={stylesSettings.settingsListRow}>
<div class={clsx(stylesSettings.settingsListCell, styles.topicsListItem)}>
<input type="checkbox" name="checkbox7" id="checkbox7" />
<label for="checkbox7" />
</div>
<label for="checkbox7" class={styles.settingsListCell}>
Общество
</label>
</div>
<div class={stylesSettings.settingsListRow}>
<div class={clsx(stylesSettings.settingsListCell, styles.topicsListItem)}>
<input type="checkbox" name="checkbox8" id="checkbox8" />
<label for="checkbox8" />
</div>
<label for="checkbox8" class={styles.settingsListCell}>
Личный опыт
</label>
</div>
</div>
<br />
<p>
<button class="button button--submit">Сохранить настройки</button>
</p>
</form>
</div>
</div>
</div>
</div>
</PageWrap>
)
}
// for lazy loading
export default ProfileSubscriptionsPage

View File

@ -1,7 +1,17 @@
h4 {
h4,
h5 {
font-weight: 500;
}
h4 {
@include font-size(2.4rem);
}
h5 {
@include font-size(1.7rem);
margin: 0 0 0.8rem;
}
.avatarContainer {
border-radius: 100%;
overflow: hidden;
@ -103,3 +113,77 @@ h4 {
.discoursNameField {
flex: 1;
}
.leftNavigation {
top: 9rem !important;
}
.passwordToggleControl {
position: absolute;
right: 1em;
transform: translateY(-50%);
top: 50%;
}
.passwordInput {
padding-right: 3em !important;
}
.searchContainer {
margin-top: 2.4rem;
}
.searchField {
display: block;
label:first-child {
opacity: 0.5;
position: absolute;
right: 1em;
transform: translateY(-50%);
top: 50%;
}
}
.topicsList {
label {
@include font-size(1.7rem);
}
}
.topicsListItem {
padding-right: 1.5rem !important;
}
.socialButton {
color: #000;
display: flex;
padding: 0.8em 1em;
transition: background-color 0.3s, color 0.3s;
&:hover {
background: #000;
color: #fff;
}
img {
vertical-align: middle;
}
.icon {
margin-right: 1em;
}
}
.socialButtonApple {
&:hover {
.icon {
filter: invert(1);
}
}
.icon {
filter: invert(0);
transition: filter 0.3s;
}
}

View File

@ -33,6 +33,8 @@ import { InboxPage } from './Pages/InboxPage'
import { LayoutShoutsPage } from './Pages/LayoutShoutsPage'
import { SessionProvider } from '../context/session'
import { ProfileSettingsPage } from './Pages/profile/ProfileSettingsPage'
import { ProfileSecurityPage } from './Pages/profile/ProfileSecurityPage'
import { ProfileSubscriptionsPage } from './Pages/profile/ProfileSubscriptionsPage'
// TODO: lazy load
// const SomePage = lazy(() => import('./Pages/SomePage'))
@ -60,7 +62,9 @@ const pagesMap: Record<keyof Routes, Component<PageProps>> = {
principles: PrinciplesPage,
termsOfUse: TermsOfUsePage,
thanks: ThanksPage,
profileSettings: ProfileSettingsPage
profileSettings: ProfileSettingsPage,
profileSecurity: ProfileSecurityPage,
profileSubscriptions: ProfileSubscriptionsPage
}
export const Root = (props: PageProps) => {

View File

@ -1,4 +1,4 @@
import '../../styles/FeedSettings.scss'
import styles from '../../styles/FeedSettings.module.scss'
import { t } from '../../utils/intl'
// type FeedSettingsSearchParams = {
@ -27,20 +27,20 @@ export const FeedSettingsView = (_props) => {
</li>
</ul>
<div class="settings-list">
<div class="settings-list__row">
<div class={styles.settingsList}>
<div class={styles.settingsListRow}>
<h2>Общее</h2>
</div>
<div class="settings-list__row">
<label for="checkbox1" class="settings-list__cell">
<div class={styles.settingsListRow}>
<label for="checkbox1" class={styles.settingsListCell}>
Комментарии к&nbsp;моим постам
</label>
<div class="settings-list__cell">
<div class={styles.settingsListCell}>
<input type="checkbox" name="checkbox1" id="checkbox1" />
<label for="checkbox1" />
</div>
<div class="settings-list__cell">
<div class={styles.settingsListCell}>
<input
type="checkbox"
name="checkbox1-notification"
@ -51,15 +51,15 @@ export const FeedSettingsView = (_props) => {
</div>
</div>
<div class="settings-list__row">
<label for="checkbox2" class="settings-list__cell">
<div class={styles.settingsListRow}>
<label for="checkbox2" class={styles.settingsListCell}>
новые подписчики
</label>
<div class="settings-list__cell">
<div class={styles.settingsListCell}>
<input type="checkbox" name="checkbox2" id="checkbox2" />
<label for="checkbox2" />
</div>
<div class="settings-list__cell">
<div class={styles.settingsListCell}>
<input
type="checkbox"
name="checkbox2-notification"
@ -70,15 +70,15 @@ export const FeedSettingsView = (_props) => {
</div>
</div>
<div class="settings-list__row">
<label for="checkbox3" class="settings-list__cell">
<div class={styles.settingsListRow}>
<label for="checkbox3" class={styles.settingsListCell}>
добавление моих текстов в&nbsp;коллекции
</label>
<div class="settings-list__cell">
<div class={styles.settingsListCell}>
<input type="checkbox" name="checkbox3" id="checkbox3" />
<label for="checkbox3" />
</div>
<div class="settings-list__cell">
<div class={styles.settingsListCell}>
<input
type="checkbox"
name="checkbox3-notification"
@ -89,19 +89,19 @@ export const FeedSettingsView = (_props) => {
</div>
</div>
<div class="settings-list__row">
<div class={styles.settingsListRow}>
<h2>Мои подписки</h2>
</div>
<div class="settings-list__row">
<label for="checkbox4" class="settings-list__cell">
<div class={styles.settingsListRow}>
<label for="checkbox4" class={styles.settingsListCell}>
добавление моих текстов в&nbsp;коллекции
</label>
<div class="settings-list__cell">
<div class={styles.settingsListCell}>
<input type="checkbox" name="checkbox4" id="checkbox4" />
<label for="checkbox4" />
</div>
<div class="settings-list__cell">
<div class={styles.settingsListCell}>
<input
type="checkbox"
name="checkbox4-notification"

View File

@ -12,8 +12,8 @@
transition: box-shadow 0.3s;
width: 100%;
&:focus {
box-shadow: 0 3px 0 #ccc;
+ label {
display: none;
}
}

View File

@ -1,20 +1,29 @@
import styles from './SearchField.module.scss'
import { Icon } from './Icon'
import { t } from '../../utils/intl'
import clsx from 'clsx'
type SearchFieldProps = {
onChange: (value: string) => void
class?: string
}
export const SearchField = (props: SearchFieldProps) => {
const handleInputChange = (event) => props.onChange(event.target.value.trim())
return (
<div class={styles.searchField}>
<div class={clsx(styles.searchField, props.class)}>
<label for="search-field">
<Icon name="search" class={styles.icon} />
</label>
<input id="search-field" type="text" onInput={handleInputChange} placeholder={t('Search')} />
<input
id="search-field"
type="text"
class="search-input"
onInput={handleInputChange}
placeholder={t('Search')}
/>
<label for="search-field">Поиск</label>
</div>
)
}

View File

@ -22,3 +22,10 @@ export type RootSearchParams = {
modal: string
lang: string
}
export type UploadFile = {
source: string
name: string
size: number
file: File
}

64
src/context/profile.tsx Normal file
View File

@ -0,0 +1,64 @@
import { createEffect, createMemo } from 'solid-js'
import { createStore } from 'solid-js/store'
import { useSession } from './session'
import { loadAuthor, useAuthorsStore } from '../stores/zine/authors'
import { apiClient } from '../utils/apiClient'
import type { ProfileInput } from '../graphql/types.gen'
const submit = async (profile: ProfileInput) => {
try {
await apiClient.updateProfile(profile)
} catch (error) {
console.error(error)
}
}
const useProfileForm = () => {
const { session } = useSession()
const currentSlug = createMemo(() => session()?.user?.slug)
const { authorEntities } = useAuthorsStore({ authors: [] })
const currentAuthor = createMemo(() => authorEntities()[currentSlug()])
const [form, setForm] = createStore<ProfileInput>({
name: '',
bio: '',
userpic: '',
links: []
})
createEffect(async () => {
if (!currentSlug()) return
try {
await loadAuthor({ slug: currentSlug() })
setForm({
name: currentAuthor()?.name,
bio: currentAuthor()?.bio,
userpic: currentAuthor()?.userpic,
links: currentAuthor()?.links
})
} catch (error) {
console.error(error)
}
})
const updateFormField = (fieldName: string, value: string, remove?: boolean) => {
if (fieldName === 'links') {
if (remove) {
setForm(
'links',
form.links.filter((item) => item !== value)
)
} else {
setForm((prev) => ({ ...prev, links: [...prev.links, value] }))
}
} else {
setForm({
[fieldName]: value
})
}
}
return { form, submit, updateFormField }
}
export { useProfileForm }

View File

@ -1,19 +0,0 @@
import { gql } from '@urql/core'
// WARNING: need Auth header
export default gql`
mutation ProfileUpdateMutation($user: User!) {
profileUpdate(user: $user) {
error
token
user {
_id: slug
name
slug
userpic
bio
# links
}
}
}
`

View File

@ -0,0 +1,13 @@
import { gql } from '@urql/core'
// WARNING: need Auth header
export default gql`
mutation ProfileUpdateMutation($profile: ProfileInput!) {
updateProfile(profile: $profile) {
error
author {
name
}
}
}
`

View File

@ -189,5 +189,19 @@
"create_group": "Создать группу",
"discourse_theme": "Тема дискурса",
"cancel": "Отмена",
"group_chat": "Общий чат"
"group_chat": "Общий чат",
"Profile settings": "Настройки профиля",
"Here you can customize your profile the way you want.": "Здесь можно настроить свой профиль так, как вы хотите.",
"Userpic": "Аватар",
"Name": "Имя",
"Your name will appear on your profile page and as your signature in publications, comments and responses.": "Ваше имя появится на странице вашего профиля и как ваша подпись в публикациях, комментариях и откликах",
"Address on Discourse": "Адрес на Дискурсе",
"Sorry, this address is already taken, please choose another one.": "Увы, этот адрес уже занят, выберите другой",
"Introduce": "Представление",
"About myself": "О себе",
"How can I help/skills": "Чем могу помочь/навыки",
"Where": "Откуда",
"Date of Birth": "Дата рождения",
"Social networks": "Социальные сети",
"Save settings": "Сохранить настройки"
}

View File

@ -0,0 +1,12 @@
---
import Prerendered from '../../main.astro'
import { Root } from '../../components/Root'
import { initRouter } from '../../stores/router'
const { pathname, search } = Astro.url
initRouter(pathname, search)
---
<Prerendered>
<Root client:load />
</Prerendered>

View File

@ -0,0 +1,12 @@
---
import Prerendered from '../../main.astro'
import { Root } from '../../components/Root'
import { initRouter } from '../../stores/router'
const { pathname, search } = Astro.url
initRouter(pathname, search)
---
<Prerendered>
<Root client:load />
</Prerendered>

View File

@ -28,6 +28,8 @@ export interface Routes {
expo: 'layout'
inbox: void // TODO: добавить ID текущего юзера
profileSettings: void
profileSecurity: void
profileSubscriptions: void
}
const searchParamsStore = createSearchParams()
@ -55,7 +57,9 @@ const routerStore = createRouter<Routes>(
termsOfUse: '/about/terms-of-use',
thanks: '/about/thanks',
expo: '/expo/:layout',
profileSettings: '/profile/settings'
profileSettings: '/profile/settings',
profileSecurity: '/profile/security',
profileSubscriptions: '/profile/subscriptions'
},
{
search: false,

View File

@ -1,4 +1,4 @@
.settings-list {
.settingsList {
display: table;
h2 {
@ -20,7 +20,7 @@
height: 2.8rem;
position: static;
width: 2.8rem;
vertical-align: middle;
vertical-align: bottom;
}
}
@ -46,11 +46,11 @@
}
}
.settings-list__row {
.settingsListRow {
display: table-row;
}
.settings-list__cell {
.settingsListCell {
display: table-cell;
padding: 0 0.5em 1em 0;

View File

@ -10,7 +10,9 @@ import type {
QueryLoadMessagesByArgs,
MutationCreateChatArgs,
MutationCreateMessageArgs,
QueryLoadRecipientsArgs
QueryLoadRecipientsArgs,
User,
ProfileInput
} from '../graphql/types.gen'
import { publicGraphQLClient } from '../graphql/publicGraphQLClient'
import { getToken, privateGraphQLClient } from '../graphql/privateGraphQLClient'
@ -42,6 +44,7 @@ import shoutsLoadBy from '../graphql/query/articles-load-by'
import shoutLoad from '../graphql/query/article-load'
import loadRecipients from '../graphql/query/chat-recipients'
import createMessage from '../graphql/mutation/create-chat-message'
import updateProfile from '../graphql/mutation/update-profile'
type ApiErrorCode =
| 'unknown'
@ -213,6 +216,10 @@ export const apiClient = {
console.debug('getAuthor', response)
return response.data.getAuthor
},
updateProfile: async (input: ProfileInput) => {
const response = await privateGraphQLClient.mutation(updateProfile, { profile: input }).toPromise()
console.debug('updateProfile', response)
},
getTopic: async ({ slug }: { slug: string }): Promise<Topic> => {
const response = await publicGraphQLClient.query(topicBySlug, { slug }).toPromise()
return response.data.getTopic

View File

@ -2818,6 +2818,13 @@
dependencies:
"@solid-primitives/rootless" "^1.2.0"
"@solid-primitives/upload@^0.0.105":
version "0.0.105"
resolved "https://registry.yarnpkg.com/@solid-primitives/upload/-/upload-0.0.105.tgz#cf415667c0fae842dbf2470554dc0c28e8ba4fac"
integrity sha512-991xLetzr25NIeuAtWpYmJSA7lJ0HSOJT9sl3sRtgpR4+QJEDIsM4lw2iYYpw7XUFGBqqX2CHI5TitvYzy/Maw==
dependencies:
"@solid-primitives/utils" "^4.0.0"
"@solid-primitives/utils@^3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@solid-primitives/utils/-/utils-3.1.0.tgz#52edf36dabe62eba94f8356c3b9b788234d088a8"