Реклама ООО Таймвэб
Реклама ООО Таймвэб
Реклама ООО Таймвэб

Создаем блог на Svelte. Часть 9: Профиль пользователя

Обсудить
Создаем блог на Svelte. Часть 9: Профиль пользователя

Продолжаем доделывать блог на базе SvelteKit. В этот раз создадим отдельную страничку для пользователя, где будет отображаться основная информация о нем, о написанных статьях, о количестве лайков. Также там будет аналог био из социальных сетей и аватарка. 

Предыдущая статья: Блог на Svelte. Часть 8: Лайки

Ссылка на профиль пользователя 

Начнем с добавления отдельной страницы, где и будет храниться основная информация о пользователе. То есть новый автоматически генерируемый роут, для разных пользователей показывающий разные данные из базы, соответствующие определенному id, почтовому адресу или никнейму. 

Для этого создадим директорию profile в папке routes, а в ней создадим файл [profile].svelte. 

Новый компонент для данных профайла

Затем нужно добавить ссылку на соответствующий роут в файле index.svelte, где сейчас отображается приветствие с указанием электронной почты пользователя. Информацию берем из хранилища user и ее же привязываем. При желании вы можете выгрузить другие данные о текущем пользователе и использовать в качестве ссылки уже их. 

<a href={`/profile/${$user.email}`}>

Ссылка с переходом на страницу профиля

Страница есть, но она пока что пустая. 

Комьюнити теперь в Телеграм
Подпишитесь и будьте в курсе последних IT-новостей
Подписаться

Список статей пользователя 

Похожий фильтр уже есть, но все же принято показывать пользователю список написанных им статей прямо в профиле. Такое работает и в Комьюнити, и на многих других площадках. 

Напишем функцию для фильтрации списка статей и отрендерим его на странице profile.

Функция onMount для запроса постов

  • Импортируем базу данных Supabase в компонент профиля: import supabase from '$lib/db'

  • Затем импортируем функцию onMount следующим образом: import { onMount } from 'svelte'

  • И user из хранилища: import { user } from '$lib/stores'

  • Создаем переменную, в которой будут храниться все посты. Пусть по умолчанию это будет пустой массив. Так получится избежать ошибок при загрузке профиля без статей: let posts = []

  • Потом делаем запрос к базе данных:

let { data, error } = await supabase.from('Posts').select('*').eq('userId', $user.id)
  • И записываем полученную информацию в переменную posts следующим кодом: posts = data

Остается лишь отрисовать полученный контент на странице. Благо для этого у нас есть компонент Post, который мы импортируем в [profile].svelte: import Post from '../Post.svelte'

А потом добавляем блок {#each} с перебором все статей: {#each posts as post} <Post {...post} /> {/each}

Компонент для отображения постов

Готово. Статьи отобразятся в том же виде, что и на главной. И у них по умолчанию будут работать ссылки на полные версии статей.

Фото профиля

Ранее мы уже добавляли картинки на сайт, но закрепляли их за статьями. Повторим ту же схему, но теперь для изображений профиля, которые сможет поставить пользователь в качестве своего аватара. Это же фото можно будет показывать в списке комментариев или же под статьями рядом с именем автора. Вариантов масса. 

Сначала создадим новый столбец в базе данных под названием profilePicture. Он нужен, чтобы привязать изображение к конкретному человеку с его логином и паролем. Оставляем тип text и позволяем использовать значение NULL.

Колонка для изображений профиля

Теперь возвращаемся к коду и создаем картинку по умолчанию. Это нужно, чтобы даже у пользователя без аватарки хоть что-то в качестве нее отображалось. Это может быть хоть пустой кружок, но лучше придумать что-то более оригинальное. 

Я просто возьму фавиконку из раздела static, которая лежит в базовом проекте SvelteKit. 

Создаем переменную imageSource и указываем в ней путь до нужной картинки. 

let imageSource = "../../../static/favicon.png"

Переменные для отображения картинок

Затем создаем в разметке элемент, в котором будет отображаться картинка (тег img), и в качестве источника изображения выбираем imageSource

<img width=60 height=60 src={imageSource} alt />

Элемент, отображающий картинку профиля

Теперь у каждого пользователя вместо аватарки будет логотип Svelte. Но только до тех пор, пока он сам не поменяет изображение. Сейчас дадим ему такую возможность. 

Для начала создадим интерфейс для загрузки изображений в браузер. Добавим переменную file в [profile].svelte: let file

Переменные для выгрузки изображений

Потом добавим input, привязанный к переменной file. Его можно разместить рядом с картинкой:

<input bind:this={file} type="file" />

input с типом file

Для добавления изображений нам нужно придумать им название. Я решил, что отличным выбором будет имя пользователя, а лучше его никнейм вкупе с названием самого файла. Поэтому нам нужно вытащить из базы данных nickname пользователя. Для этого в onMount нужно сделать соответствующий запрос к Supabase и сохранить полученное значение в переменную userName.

Функция загрузки контента в пост

  • Создаем переменную userName: let userName = ""

  • Внутри onMount делаем запрос к БД. Мы запрашиваем пользовательские данные, ориентируясь на id пользователя, зашедшего на сайт:

let {data: userInfo} = await supabase.from('Users').select('*').eq('userId', $user.id)
  • Затем меняем значение переменной userName на nickname пользователя: userName = userInfo[0].nickname

Далее создадим функцию загрузки изображений, которая будет активироваться автоматически при добавлении новой картинки на сервер Supabase. 

Функция загрузки изображений

  • Прописываем асинхронную функцию uploadImage вот так: const uploadImage = async ( ) => { }

  • В тело функции записываем информацию о будущем изображении, взяв за основу файл, привязанный к переменной filelet image = file.files[0]

  • Потом записываем разрешение картинки: const imageExt = image.name.split('.').pop()

  • Следующий шаг – формирование имени загружаемой картинки: const imagename = `${userName}${file.name}.${imageExt}`

  • Нужные переменные сгенерированы, и можно сделать первый запрос к БД. Сначала загрузим картинку на сервер: 

const { data, error } = await supabase.from('pictures').upload(imageName, image)
  • Затем запрашиваем информацию об уже загруженных изображениях. В частности той, что мы только что отправили в БД: 

const { data: images } = await supabase.storage.from('pictures').getPublicUrl(imageName)
  • Обновляем информацию об аватарке пользователя в базе данных: 

let { data: profilePicture } = await supabase.from('Users').update({profilePicture: images.publicURL}).eq('userId', $user.id)
  • А потом переадресуем пользователя на ту же страницу, чтобы он видел внесенные изменения: goto(#)

Пользователь увидит свою аватарку, так как мы заменяем значение imageSource на userInfo[0].profilePicture при первичной загрузке страницы профиля, и делаем это только при условии, что в базе данных есть какая-то запись. В противном случае отображается логотип Svelte, указанный по умолчанию. 

Назначение переменной изображения

Описание профиля

Еще одна часть интерфейса страницы пользователя, часто встречающаяся в сети – это био. То есть краткое описание, которое дает пользователь о себе, чтобы о нем было известно чуть больше, чем ничего. 

Как обычно, начинаем с созданием подходящего столбца в Supabase. Назовем его description. Это будет текст, который может иметь значение NULL.

Колонка description

Потом создаем аналогичную переменную в коде на странице [profile].svelte. По умолчанию она может быть пустой. Это не играет роли. 

Описание пользователя мы будем подтягивать через функцию onMount

  • В асинхронном блоке onMount сначала прописываем запрос к базе, а точнее именно к пункту description того пользователя, что сейчас зашел на сайт. 

let { data: desc } = await supabase.from('Users').select('description').eq('userId', $user.id)
  • Меняем значение переменной description на данные, полученные из запроса к Supabase: description = desc[0].description

Теперь нужно сделать интерфейс для отображения описания и для его изменения. Объединим его в единый блок, отвечающий сразу за обе функции.

Интерфейс добавления описания

  • Создаем div-блок, внутрь которого помещаем textarea. Значением textarea будет содержимое переменной description

<textarea bind:value={description}></textarea>
  • И тут же добавляем кнопку для сохранения изменений, внесенных в textarea. Это будет кнопка, привязанная к функции saveDescription (ее мы создадим позже). 
<button on:click={saveDescription}>Save</button>

Осталось сделать метод для обновления description в базе данных. 

Функция обновления описаний в базе данных

  • Прописываем асинхронную функцию saveDirection вот так: const saveDirection = async ( ) => { }

  • В теле saveDirection делаем запрос к базе с обновлением столбика description

let { data, error } =await supabase.from('Users').update({description: description}).eq('userId', $user.id)

Теперь на странице пользователя появится поле с описанием. Можно его отредактировать в любой момент, а по нажатию на кнопку Save сохранить новый текст в БД.

Список лайков

Теперь разберемся с блоком лайков на странице пользователя. Здесь мы сделаем сразу две вещи:

  1. Укажем, сколько вообще лайков получили посты пользователя (то есть те, что написаны автором, которым являетесь вы).

  2. Сделаем блог, в котором отображаются все статьи, которые вам понравились (пока без отдельной страницы). 

Начнем с первого пункта.

Количество лайков от других пользователей

Чтобы отобразить лайки для конкретного человека, нужно просто обратиться к ключу likes в объекте data, который мы получаем еще при первоначальной загрузке страницы, когда выводим список постов. 

Сначала создадим переменную likes и прировняем ее к нулю. Будем использовать ее, чтобы где-то хранить количество лайков. 

Переменная с количеством лайков

Потом пробегаемся по всем статьям в data и вытаскиваем длину массивов likes для каждой из них. Также складываем все строчные значения через символ "+" методом join и подсчитываем общую сумму длины массивов. Звучит сложно, но на практике реализуется одной простой строчкой: 

likes = eval(data.map(e => e.likes.length).join('+'))

Подсчет лайков

Осталось сделать хоть какой-то интерфейс, который покажет нам количество пользовательских лайков. 

Это будет обычный div с svg в форме сердечка и подписью со значением переменной likes. Структура: <div>{likes}</div>

Интерфейс для отображения лайков

Теперь вы точно будете знать, сколько раз вас лайкали (подсчитав всех пользователей, лайкнувших ваши посты). 

Все лайкнутые посты (сохраненные в избранное)

Еще один вариант применения лайков – хранение статей, которые понравились (например, чтобы вернуться к ним позже). В профиле часто есть отдельный раздел для таких материалов. Мы не будем хранить ссылки на них или делать отдельную страницу, а лишь сделаем специальный блок, показывающий названия тех статей, что вам когда-то приглянулись. 

Для этого добавим в код переменную likedPost, которая будет пустым массивом по умолчанию: let likedPosts = []

Массив с лайками

Затем внутри функции onMount добавляем высчитывание лайкнутых статей. Мы будем делать это с помощью алгоритма поиска собственного ника среди пользователей, лайкнувших тот или иной пост. То есть мы переберем все статьи и отфильтруем те, на которых стоит наше сердечко:

 likedPosts = data.filter(e => e.likes.includes(userInfo[0].nickname))

Для примера возьмем массив статей из data (то есть свои же), но вы можете сделать запрос ко всем материалам на сайте.

Ну и в заключение надо сделать какое-то графическое отображение лайкнутых постов. Я сделал простейший список с названиями, но сюда же можно прицепить ссылку или любой другой контент:

Блок перебора лайкнутых постов

Вот что у нас получается:

Страница профиля

Пока не очень симпатично, но это временное явление. Мы будем дизайнить блог в следующих материалах

Вместо заключения 

Еще одна важная составляющая блога завершена. Мы приближаемся к концу. Осталось приделать комментарии, и можно переходить к «уборке». Нам нужно зарефакторить код, чтобы его дальнейшая модификация и добавление новых функций не вызывали боль ни у нас, ни у других разработчиков, которые могут в будущем столкнуться с работой над вашим сайтом. 

Обязательно делайте коммиты и изучите предыдущие материалы из этого цикла, ведь многие концепции, затрагиваемые здесь, объяснялись ранее. Удачи в разработке!

Продолжение: Блог на Svelte. Часть 10: Комментарии

Hello World! Гайды и обзоры для девелоперов разных мастей.

Комментарии

С помощью соцсетей
У меня нет аккаунта Зарегистрироваться
С помощью соцсетей
У меня уже есть аккаунт Войти
Инструкции по восстановлению пароля высланы на Ваш адрес электронной почты.
Пожалуйста, укажите email вашего аккаунта
Ваш баланс 10 ТК
1 ТК = 1 ₽
О том, как заработать и потратить Таймкарму, читайте в этой статье
Чтобы потратить Таймкарму, зарегистрируйтесь на нашем сайте