🚀 CRM система с нуля на VUE 3 / NUXT [Vue Query / VeeValidate / Pinia / Shadcn / AppWrite / TS]

🚀 CRM система с нуля на VUE 3 / NUXT [Vue Query / VeeValidate / Pinia / Shadcn / AppWrite / TS]05:14:37

Информация о загрузке и деталях видео 🚀 CRM система с нуля на VUE 3 / NUXT [Vue Query / VeeValidate / Pinia / Shadcn / AppWrite / TS]

Автор:

RED Group

Дата публикации:

11.12.2023

Просмотров:

83.7K

Транскрибация видео

Ребят, всем привет, с вами Макс, канал Red Group, и это новый ролик в нашем канале, это возвращение, так сказать, к нашим истокам, к старой рубрике «Большие проекты».

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

Я объясню, что мы здесь будем делать.

Мы здесь разработаем полноценную CRM-систему на Vue.js, слэш, Накст.

В Vue.js будет третья версия, все будет свеженько, круто, удобно и так далее.

И я вам покажу глазами React разработчик, как выглядит Vue и как в нем разобраться.

И этот ролик будет полезен не только для тех, кто любит Vue, а также для тех, кто пишет на React, потому что вам поможет это как-то разгрузить вашу голову и посмотреть на ситуацию со стороны.

Тем самым в ваших проектах вы можете прийти к новым решениям.

Ну, конечно же, самое полезное будет это тем, кто изучает Vue.js, потому что ролик прям легендарный и крутой.

Вы давно меня просили снять что-нибудь на Vue.js, я все отнекивался, но вот пришел тот момент, я подготовил для вас крутой проект, которым готов поделиться.

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

Давайте в честь этого, в честь запуска такого крутого, грандиозного проекта, чтобы я не выгорел, да, и продолжил сделать для вас такие крутые ролики, просьба

В комментариях опуститься.

И давайте прожмем очень много огоньков.

Ставьте просто эмоджи огоньки.

Как в старые добрые времена.

В топе максимальное.

И наберем большое количество комментариев и лайков.

Это будет очень круто.

И как обычно по нашим правилам.

Набираем 2000 лайков на этом ролике.

И я снимаю следующий большой проект.

Он уже будет скорее всего на Next.js.

И скорее всего это будет Red Planner.

Но там посмотрим как будет по плану.

Так что ребят все зависит от вас.

2000 лайков ставим ниже.

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

Итак, стек технологии.

Nux.js, третья версия.

Потом Nux.Icon, Tailwind, Vue, понятное дело, третья версия.

На XUI немножечко будем использовать некоторых моментов.

Opinion в качестве стейт-менеджера будем использовать.

ViewQuery, куда же без него, мой любимый, как и в React, я люблю React Query.

Здесь ViewQuery от тех же разработчиков, TanStack.

Дальше, VValidate для валидации формы.

AppWrite в качестве бэкэнда будем использовать, опять же, вы многие просили, теперь мы его воспользуемся, я дам в конце, кстати, в конце ролика, ребят, я дам мнение и по Vue.js, и по AppWrite, и по всему тому, что новенькое на нашем канале, чтобы вы понимали, использовать в своем проекте, либо не использовать, и услышали мое какое-то мнение по этому поводу.

Дальше, SLSX, классика, да, вариативность классов, когда мы делаем, да, кондишены.

Day.js для работы с датами, JS cookie для работы с куками.

И опять же, многие просили, называется там, по-моему, ShadenCN, или как так называется, UI-библиотека, которая не совсем UI, она такая, как бы, больше такой генератор компонентов.

Вот вы многие просили, опять же, поэтому будем использовать.

Короче, в ролике дальше глянете, я не помню, как называется, просто ее нету в зависимостях, непосредственно в проекте.

Ну, и, собственно, Tailwind, я уже сказал, да, и UID для генерации ID-шников.

Там вроде бы AppWrite сам предоставляет всякие штуки для генерации ID-шников, но я решил использовать UID, потому что, ну, те, короче, не работали, вот, а это отличная замена.

На этом вот касаемо стека, я думаю, понятно.

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

Короче, это будущее, это просто вынесет всю IT-сферу, ребят, я уверен, потому что то, что я придумал, это будет круто, это будет очень круто, скоро ждите обновления DarkSide, платформы, кайфанете, вот.

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

Что вы получаете по общей подписке, да, подписки есть разные уровни по длительности.

Разберем с вами базовый уровень.

Ребят, и кстати говоря, я хотел вам сообщить о том, что мы цены сбросили немножечко, да, потому что мы их поднимали в связи с курсом.

Курс немного упал, мы цены сбросили примерно на 10-15%.

Раньше тариф на 1 месяц стоил 3600 примерно, да, сейчас он стоит 3290 рублей.

И за эти деньги что вы получаете?

7 интенсив разного уровня.

Из них это JavaScript для всех интенсив, где вы освоите базу JavaScript, которая вам нужна для любого проекта.

В том числе, если вы на Vue.js разрабатываете, на React, на Svelte, на Angular, без разницы на чем.

Там дается прям база JavaScript.

Мы на чистом, на голом JavaScript написали полноценное банковское приложение.

Что может быть лучше для практики вот такого прям базиса, вот такого каркаса?

Что может быть лучше?

Я считаю, что ничего лучше не может быть.

Это самый лучший интенсив на платформе.

Дальше есть интенсив Back для начинающих.

Там мы разработали...

Бэкэнд для приложений для тренировок на чистое Node.js плюс Postgres и так далее.

То есть прям полноценный бэкэнд для начинающих.

Потом фронт для начинающих.

Они идут в связке в такой, да, там уже React мы изучили.

И это у нас такой интенсив по разработке приложений для тренировок.

Потом есть фронт для продвинутых и бэк для продвинутых.

Это, соответственно, два отдельных интенсива, но они, опять же, в связке работают.

Можно и по отдельности, можно и в связке.

Там мы также готовый backend даем.

Это интенсив, в котором мы разработали полноценный онлайн кинотеатр.

На экране вы все это время видите все эти проекты.

Они прям шикарные.

Это прям пушечка.

Это идеальная практика, ребят.

Вот что я могу дать, то я вам даю.

Я могу дать идеальную практику.

Вот я ее как раз к вам и даю.

Также, ребят, у нас есть интенсив по мобильной разработке на React Native.

И еще есть также интенсив по верстке.

То есть прям полноценная...

линейка 7.7.

Кроме этого, мы стали добавлять еще новые приколюхи.

То есть, и так уже дофига за 3290 рублей, ребят.

Ну, еще сверху мы вам накидываем проекты с нуля.

А именно, там есть проект с нуля.

Это Amazon 2.0.

Мы его смонтировали.

Там 43 урока, по-моему, где вы полноценно с нуля напишите интернет-магазин.

Просто конфетка там с оплатой, с реализацией админ-панели.

Просто пушка.

Кроме этого, поддержка в веб-чате.

У нас 24 на 7, вы можете писать вопросы в веб-чате, там вам легко помогут ответить на ваши вопросы.

И исходники больших проектов.

У нас там около 70, либо 80 даже репозиториев на платформе лежит, которые можете качать и пользоваться.

Там вот идеальные примеры всех проектов, которые мы делали.

И они все доступны в платном формате.

То есть вы оплачиваете подписку, получаете к ним доступ.

И на некоторых других тарифах также есть закрытый клуб «Пять утра».

То есть вы тоже можете залететь, значит, там скидывать каждое утро скриншоты, как вы встаете в 5 утра, да, и, собственно, вставать в 5 утра, значит, у нас примерно так происходит там общение.

Но это, опять же, не обязательно, это на других тарифах, кому это надо, вдруг залетаете также по общей подписке.

Кроме этого, мы сейчас начинаем там выкладывать еще один курс, уже вышло 4 урока, на момент видео, скорее всего, 5 уже выйдет, будет всего 30 уроков.

Полноценный курс, как мы пишем саму платформу, то есть вот как будет написано вот эта новая версия платформы, там оно все будет смонтировано в красивом виде.

так что ребят мы очень много делаем для наших премиум подписчиков и цена небольшая 3290 рублей ребят это вообще там два раза магазин сказать это супер выгодная цена для нашей сферы это просто лакомый кусочек потому что в основе на стоит 50 100 тысяч курс и там всякий skill боксик бренд и так далее здесь же мы за небольшую стоимость даем просто огромное количество полезного контента ребят и это очень важно наш компании нашего роста мы хотим мы у нас небольшая команда мы хотим развиваться мы хотим становится больше и крупнее опять же

Я вам обещаю, что DarkSide, новая платформа, прям все взорвет.

Да, ну нужно время, нужно нам больше времени, потому что команда небольшая, нужно чуть-чуть побольше времени.

Но как только мы это сделаем, ребят, цена тоже поднимется, поэтому лучше купить подписку сейчас.

Особенно тариф на всегда, его потом в будущем больше не будет.

Это невыгоден тариф для бизнеса.

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

Но потом, после выхода новой версии платформы...

данного тарифа не будет.

То есть у тех, у кого он был, останется.

У новых не будет возможности его купить.

Короче, такая штука, ребят.

Ссылка в описании.

Залетайте.

Это будет лучшим приобретением в этом году и в следующем году.

И в любом году, короче, ребят.

Не упускайте свою возможность стать реально крутым разработчиком.

Ссылку я оставлю в описании.

Кстати, ребят, я в этот раз пробую записывать через ОБСку параллельно, надеюсь, будет комфортно все смотреться, потому что, не знаю, короче, оцените, пожалуйста, в комментариях качество звука, качество картинки, насколько он на высшем уровне, либо что-то стоит подправить, я прислушаюсь к критике, и если что, исправим в какой-то момент.

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

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

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

Итак, демо проекта.

Демо проекта видим на экране.

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

Как ни странно, выйдем из системы, кнопку логаут нажмем, нажимаем, вот происходит разлогин, выходим из системы.

И дальше можно либо войти, либо регистрироваться.

Вот одна и та же форма и для того, и для другого, просто для регистрации есть еще регистрация.

Давайте напишем e-mail какой-нибудь условным.

Так, ну давай здесь напишем просто там test, собака, test.ru.

У меня, по-моему, такой логин, я, честно говоря, уже не помню.

Так, пароль от 1 до 8 и нажимаем логин.

Идет загрузка общая нашего приложения и вот наш приложение загрузилось.

Как видим, мы получаем такой Kanban-борд.

Здесь потом переименуем, я думаю, по-другому напишем.

Это я так для теста писал.

Собственно, это полноценная CRM-система.

Что такое CRM-система?

CRM-система – это когда мы умеем управлять нашими клиентами заказами.

И откуда у меня вообще есть в этом опыт?

Давайте с этого начнем.

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

Хранить там разработку сайтов, разработку дизайна, SEO-оптимизацию, маркетинг и так далее.

Вот мы это все хранили в CRM-системе.

Я перепробовал и Bitrix24, и AmaCRM, и какие-то там еще NVCRM, по-моему, была.

Ну, короче, вот разные вариации.

И мне, как программисту, понятное дело, ни одна не понравилась.

Потому что я люблю делать свое.

Короче, не будем дальше гнать воду, погнали дальше.

Извините за тавтологию.

Собственно, вот такой мы видим Kanban Board, то есть Kanban это вот в целом вид, который мы сейчас наблюдаем, это наша CRM-система.

То есть мы видим статусы, это входящие, это на согласовании, в производстве, произведено и к отгрузке.

Как видите, в зависимости от статуса у нас меняется цвет, то есть сначала у нас такой цвет серый, потом чуть он фиолетовенький становится наш Primary цвет, Primary главный цвет я имею в виду, да, и окончание мы видим уже непосредственно самый такой яркий фиолетовый цвет.

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

Далее, мы, понятное дело, можем перетаскивать между колонками эту всю историю, да, то есть, допустим, берем дизайн на заказ и перетаскиваем сюда.

Подождем ответа сервера, и, собственно, вот она перетащилась, пожалуйста, анимация, все красиво перетащилась.

Почему происходит задержка, я вам объясню.

Мы пользуемся AWP-врайт, я сейчас покажу эту консольку.

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

Если ты пишешь свой backend, когда ты его пишешь, он отвечает максимально быстро, потому что это backend, который написал ты сам, и там нет лишнего ничего.

Там есть только то, что нужно твоему проекту.

И это важно понимать.

Из-за того, что мы используем app-write, он производит очень много действий под капотом, которые нам абсолютно не нужны.

Поэтому время ответа очень долго.

А здесь у нас каким образом все происходит?

Я потом, понятное дело, в коде все вам расскажу, объясню, как это все дело происходит.

Ну, вкратце, при перетаскивании происходит запрос на сервак, где мы обновляем нашу базу данных.

Оно автоматически с ней синхронизируется и с помощью Refetch получаем новые данные и сделаем перерендер данного компонента.

И тем самым у нас обновляется уже стейт, вот здесь появляется наша карточка.

Как это можно исправить?

Смотрите, мы это дальше разберем в ролике, я вам покажу вариацию, как это можно исправить.

Я не стал этого делать, потому что я не люблю такие штуки делать.

Я люблю, когда полный идет синхронайз с базой данных.

Люблю, чтобы данные на клиенте совпадали с данными на стороне базы данных, чтобы не было какого-то конфликта определенного, чтобы у нас было состояние, ну, равно, скажем так, да.

Но вообще, как это можно исправить?

Мы могли бы сделать, отдельно идет запрос на БДшку, и на клиенте мы отдельно еще делаем, ну, просто на клиенте передвигаем эти элементы.

Можно было так сделать, но я считаю, что это костыли, то есть это чисто ради визуала делать, не вижу смысла в этом, это лишний код.

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

И получится вообще-то трэш, и клиент потом обновит и поймет, что у него на самом деле карточка осталась на месте.

Ну, короче, вот поэтому я решил ставить таким образом.

Да, немножко есть задержка.

Опять же, повторюсь, в нормальном бэкэнде задержки практически нету.

Оно происходит моментально.

Это вот за счет бэкэнда реально долгая задержка.

Это касаемо, это понятно.

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

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

Собственно, здесь пишем наименование, да, допустим, у нас это будет, ну, давайте, Яндекс.Директ, да, реклама, или давай, реклама фитнес-клуба, фитнес-клуба.

Дальше сумма.

Ну, сумма, допустим, у нас будет там, условно, там 12 тысяч рублей, ну, такая дешевая реклама, e-mail, допустим, gym, там, собака, gym.ru и компания, у нас будет какой-нибудь фитнес-клуб, какие там у нас были фитнес-клубы, я уже не помню, там, я не знаю, gym fitness, я ничего не придумал, давай просто gym напишем, да, ну, я люблю вот так писать, чтобы как-то было более официально, о, gym, допустим, да.

Нажимаем «Добавить», загрузка идет, как видите, все, успешно добавилось.

Вот здесь идет максимально быстрый ответ.

И формочка осталась, потому что, мало ли, допустим, я ее специально не сворачиваю, потому что, мало ли, допустим, клиент захочет, ну, точнее, менеджер захочет добавить еще одну карточку сразу же.

Вот, карточка успешно добавилась.

И что самое дальше интересное?

Мы можем открыть карточку в более подробном контексте, скажем так.

Нажимаем сюда, карточка плавненько вылазит справа, красивенько, называется штука «Slide over».

Тут мы видим и наименование, и сумму, и статус, и клиенты, и дата создания, и информацию о сделке.

Также есть поле для оставления комментариев.

Это очень важно при работе CRM-системы, чтобы клиент мог, точнее, между собой менеджеры могли оставлять комментарии об определенном клиенте, чтобы опять же не терять контекста.

Или, допустим, когда менеджер созвонился с клиентом, перенесли встречу на завтра, он пишет, что встреча теперь будет завтра.

Допустим, смотри, перенес встречу...

На завтра, там, 10 утра Нажимаем Enter И, как видите, вот добавляется наш комментарий Ну, моментально все происходит, причем это все происходит сразу в базе данных Можем пойти в нашу базу данных, Database Идем сюда Так, давайте чуть назад выйдем А, ну, кстати, да, комментарий правильно зашел И вот, перенесу встречу на завтра 10 утра Вот наш комментарий, пожалуйста, остается в базе данных То есть все синхронизируется в автоматическом формате с базы данных Это круто И переклики вне области, закрывается слайд овер

Касаемо этого, наверное, все.

Да, я думаю, что вроде бы ничего не забыл, вроде все красиво выглядит.

Идем дальше.

А дальше у нас еще есть раздел «Customers».

Раздел «Customers» — это раздел с нашими клиентами непосредственно.

Все наши компании, с которыми мы сотрудничаем, они располагаются в этом месте.

То есть вот наш ООО «Джим» в конце расположился красивенько, вот наша компания ООО «Спорт», логотип «Найка», и также каждого клиента можем отредактировать.

Я не стал запариваться, знаешь, там где кнопка «Эдит», я просто сделал по клику на изображение, открывается непосредственно окно редактирования той или иной компании.

Допустим, отредактируем тот самый ООО «Джим».

Нажимаем на нее, открывается, данные автоматически подгружаются с базы данных.

У нас есть еще одно поле, которое там его нет, потому что оно необязательно.

Это откуда к нам пришел клиент.

Это очень важно для CRM-систем, потому что мы должны понимать, откуда к нам пришел клиент для маркетинга.

Либо он пришел с рекламы, дальше с Яндекс.Директа, с мейл-рекламы, социальных сетей, с ВК и так далее.

Должны мы это все понимать.

Допустим, пишем соцсети.

Пришел он к нам с соцсетей.

И вот его логотипчик текущий, да, это дефолтный логотип, который вешается на каждую компанию.

Не совсем идеальный логотип, но подойдет для аватарки, скажем так.

Нажимаем «Выбрать», выбираем, допустим, «Макдональдс», вот у нас есть замечательный, как ни странно, «Джим», да, и «Макдональдс».

Ждем время, и вот загружается наш логотип «Макдональдса».

Не очень круто он подходит под круглую вариацию, но тем не менее я решил его взять.

Нажмем «Сохранить», происходит загрузка, сохранение выполнено успешно.

Теперь, как мы это можем проверить?

Либо обновить страницу условно, обновляем, да, и видим, что логотип подменился, и тут тоже подменилось поле.

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

Вот наша компания замечательная, вот о ней вся информация.

Короче, вот как-то так все это выглядит.

Вот такой проект будем разрабатывать.

Тут на самом деле много чего еще интересного под капотом сделано, то, что вы не видите так визуально, скажем так, да, но реально очень многое решает.

И этот проект, наверное, крутой.

Я думаю, что уровень этого проекта, наверное, джуниор.

Я бы не сказал, что это какой-то middle уровень, потому что тут чего-то такого прям суперсложного нету, как мне кажется.

Хотя, может быть, я ошибаюсь, мне тяжело говорить со стороны моего опыта.

Я думаю, что это все-таки уровень junior, может быть, какой-нибудь pre-middle уровень, вот что-нибудь такое.

Поэтому, если вы стремитесь к этим уровням, либо дальше, то для вас этот проект идеально подойдет.

Да, и хотел сказать, скачать проект можно по ссылке в описании.

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

Так что я решил, что исходники к этому проекту мы отдадим абсолютно бесплатно по ссылке в описании.

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

Реально, ребята, которые, допустим, имеют возможность оплатить нашу подписку, чтобы у вас была возможность скачать абсолютно бесплатно.

Напомню, раньше все исходники раздавались только по подписке.

Так что вот, а те, у кого есть подписка, ребят, не беспокойтесь, у вас будет и так миллион плюшек, поэтому не парьтесь особо.

Ссылка в комментариях, короче, качайте исходник вот этого всего проекта нашего готового, можно без проблем, ну, короче, вообще кайф.

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

Вот, опять же, помню, все абсолютно бесплатно, ссылка в описании, скачивайте, кайфуйте и так далее.

Ну, либо сначала сделайте, если что-то будут вопросы, скачивайте исходник и сравнивайте варианты.

Ребят, минуточку вашего времени, хочу сообщить, ребят, что уже 11 декабря стартует наш зимний марафон, и не спешите переключать этот кадр, да, потому что это очень важная информация.

Что важно понимать?

Вы можете купить этот марафон, оплатить уже после его начала.

То есть он начался в течение ближайших 10 дней, условно, вы можете оплатить, спокойно залететь.

Да, вы чуть пропустите начало, но вы особо ничего не потеряете, поэтому время у вас есть, ребят, кто дает этот марафон.

Вкратце, ссылка в описании, почитайте информацию всю.

Но что важно понимать, что это лучший марафон для программистов.

Вы туда приходите, вы за 2,5 месяца научитесь круто писать проекты, разрабатывать большие проекты, получите как минимум 2 проекта в портфолио.

Разные есть уровни разработки, то есть это верстка, это тренинг, это джуниор и миддл уровень.

Каждую неделю мы будем выкладывать задачи в канал.

То есть каждую среду получать новые задачи.

И у вас время до среды следующей выполнить эти задачи и отчитаться об этом в чате.

То есть такой прям практический марафон.

При котором у нас уже третий поток, да, то есть у нас был Red Summer, был Red Fall, это Red Winter и это последний поток, потому что потом мы выходим на отдых.

Ребят довольны и напомню, что в конце топ-5 лучших по выбору самих участников получат сертификаты и премиум подписки, а топ-1 получит уникальный VIP-тег, что это победитель.

Топ 1 RedWinter.

Также он получит уникальный сертификат, где будет написано, что он лучший студент.

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

Мне кажется, это прям вообще круто.

Это комбо просто.

Короче, вы и практикуетесь, и при этом получаете проекты в портфолио, и наращиваете свой потенциал, и при этом получаете призы в конце.

А, ну и крутой комьюнити еще есть параллельно, который вам всегда может подсказать и в чем-то помочь.

Мне кажется, это идеальный комьюнити.

И если бы оно было не идеальным, ребят, мы бы уже не запускали третий поток.

Нам есть на что опереться.

Давайте сейчас размещу отзывы уже со второго потока.

Чтобы вы понимали, что это реально крутой работающий продукт.

И это такой живой организм, в котором вы реально будете развиваться.

И, ребят, хочу вас не упустить возможность, потому что это последний марафон.

Больше такого не будет или будет, но не очень скоро.

Мы хотим взять отдых.

Это очень тяжело по нагрузке.

Знаете, просто что это прям крутой марафон с четырьмя разными уровнями.

Верска, тренер, джуниор, мидл с задачами на каждую неделю.

По два проекта на каждый уровень.

То есть, словно всего 8 проектов получается.

Короче, это полный кайф, ребят.

Ссылка в описании, читайте подробности и кайфуйте, наслаждайтесь.

Буду всех рад видеть на этом зимнем марафоне.

Встретим зиму вместе, ребят, и проведем ее шикарнейшим образом.

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

Шрифт у нас сегодня будет необычный максимально.

Надеюсь, он вам будет читаемым и видимым.

Я не знаю.

Ну, наверное, да, там ничего сложного нет, по идее.

Оператор MonoLeak.

Просто, ну, у меня он прикалывает.

Оператор MonoLeak.

Leak — это аллигатюрс, то есть это когда у тебя равной стрелочка превращается в один символ.

Красивенький.

Вот.

Что еще?

Мою настройку виз-кода я также опубликую.

Ссылку кину внизу.

Скачивайте также бесплатно настройку виз-кода.

Я дальше на канале выпущу ролик, скорее всего, под конец года.

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

Итак, давайте начнем с документации.

На самом деле, документация Nuxt мне очень сильно понравилась.

Если сравнить ее с Next.js в React, Nuxt гораздо приятнее.

Вот если бы я делал документацию, я бы делал ее именно так.

Мне она нравится по визуалу, очень красивая, очень крутая, очень информативная.

Короче, это топчик прям.

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

Так, значит, для начала давайте, собственно, запустим наше приложение.

Да, перейдем сюда.

У нас есть pnpm, есть ban, есть npx.

Вот, я воспользуюсь npx.

В принципе, тут все, я думаю, логично.

К pnpm я еще не перешел.

А потом, может быть, как-нибудь его попробую, не знаю.

Можете в комментах писать, если вы уже пробовали, как вам вообще на ощущениях.

Итак, вставляем в терминал.

Терминал у меня iterm с оболочкой omizeh.

Опять же, если вдруг нужен ролик про терминал, пишите в комментах, ребят.

Но не факт, что я это сниму.

Тема слишком узкая.

Так, npx.naxt.latest.init, да, и пишешь название проекта.

Название проекта у нас будет, так, у нас это CRM-систем, да, crm.system.naxt.

Я напишу рекорд, чтобы понять, что это запись видео была, да, чтобы я мог исходник вам сделать, чтобы не перепутать ни с чем.

Так, Package Manager, я буду использовать Yarn, свой любимый Yarn, собственно, здесь выбирать на свой вкус, опять же, прям совсем какие-то моменты приземленные разбирать не будем, потому что я думаю, что на уровне Naxt вы должны уже на уровне Junior более-менее что-то понимать, ну, особенно с пакетным менеджером, я думаю, объяснять не стоит, но тем не менее, у меня был на канале Shorts, по-моему, где я сравнивал как раз-таки Yarn NPM.

и, по-моему, pnpm тоже, я сравнил вот эти три штучки, и я для себя выбрал, что yarn пока что самый оптимальный, вот, ну, опять же, pnpm набирает реально высокий оборот, и, возможно, на него перейдем со временем, но посмотрим, пока что yarn, собственно, это самое лучшее решение, так, git-репозиторий мне не нужен, да, и все, проектик у нас запустился, давайте его откроем с помощью команды code crm system, system next record,

Запускаем.

Вот он запустился.

Отличненько.

Теперь закроем наш терминальчик.

Я быстренько сейчас себе здесь реорганизацию окон сделаю.

Так, вот наш проектик открылся непосредственно.

Давайте сначала разберем базовую структуру Next.

Что здесь и как я вам расскажу, как все работает.

А потом мы перейдем уже непосредственно к разработке.

Так, касаемо нашего Nux, смотрите, по структуре, давайте чуть поближе сделаю, вот так, да, чтобы вам было видно, по идее, вот так должно быть все видно.

Значит, смотрите, есть NodeModules, классика, да, сюда все пакеты ставятся, я думаю, это понятно все.

Ну, здесь ничего сложного нет.

Public, как в Next.js, да, опять же, это место, где вы складываете все свои статичные файлы, которые в будущем будут доступны по адресу, допустим, localhost 3000, слэш, название вашего файла, то есть корневая директория, где кладутся все статичные файлы.

Допустим, здесь лежит файл favicon.ico.

Дальше.

Сервер.

Сервер, собственно, здесь будет лежать все, что касаемо сервака, если вы будете работать на стороне сервера в Nuxt.

И опять же, хотел сказать по поводу Nuxt.

Здесь, конечно, не такая дружелюбная изначальная сборка, нежели Next.js.

Что я имею в виду?

То есть в Next.js вам уже дают готовую концепцию, как и что размещать.

В Nuxt.js, я когда только начал его изучать, в плане вникать, понятное дело, что мне это далось гораздо легче, вот это обучение, нежели вам.

У меня все-таки огромная база JavaScript, React и так далее.

То есть и Nexta тоже.

То есть мне это далось легко прям.

Мне стоило там один раз почитать документацию, я понял, что и куда, потому что оно плюс-минус похоже, все фреймворки, и концепция, она едина.

То есть если вы выучите условно там React, то вам легко будет и Angular выучить, и Vue.js, ну, если вы с AppCrypt работаете непосредственно, и так далее.

Поэтому по этому поводу не запаривайтесь, главная база.

Это я говорю к тому, что и вам придется посмотреть документацию.

То есть Next.js, допустим, когда ты проект сразу запускаешь, у тебя показываются конкретные папки, и говорится...

Что где должно лежать.

То есть, к примеру, у нас есть app-директор, в ней какая-то структура должна быть и так далее.

Здесь же у тебя есть конкретные папки, вот основные, но нет понимания, какие дальше должны быть папки.

А на будущее я вам такой небольшой забег скажу, спойлер, да, в Next очень важна вот эта вот, как он называется, по-моему, nesting, да, то есть когда вы правильно ставите наименование папкам и правильно делаете структуру.

Потому что в таком случае, если вы все делаете правильно, NUX позволяет вам даже не импортировать компоненты, то есть брать их просто и писать с правильным названием, да, и они как бы автоматически будут подхватываться.

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

Так, значит, дальше смотрим, gitignore, я думаю, понятно, работа с гитом, да, файлы, которые игнорировали для гита, appview, собственно, это у нас корневой файлик, да, где у нас запускается наш проект, вот, допустим, сейчас у нас запускается компонент nuxt-welcome, и этот компонент, в чем прикол, да, он лежит прямо, видите, в папочке .nux, то есть они его сюда положили, он лежит, и он, даже больше скажу, он лежит прямо в node-модульцах, да, это прямо, ну...

Супер неудобно.

То есть я, допустим, как новичок, хочу посмотреть, что там в этом файлике, а я не могу это посмотреть.

То есть могу, но это не так удобно, как в том же Next, скажем так.

И я не могу его удалить.

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

А получается, что я не могу так сделать.

Ну, это такие мелочи, скажем так.

Naxx Config TS, кстати, лучше работать, чем конфиг в Next.js, я в этом ролике, ребят, часто буду делать сравнение с реактовским Next, потому что я с ним работал много, и я думаю, некоторым из вас будет полезно это сравнение, чтобы понять, да, что лучше, что хуже и так далее, спойлер, такого нет понимания, что лучше, что хуже, да, все-таки основная концепция выбирать под команду, под проект.

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

Package.json – классический файлик, в котором у нас собраны все скрипты для запуска приложения, тип нашего приложения, как он будет работать через модули или через другие моменты.

Ну, сейчас у нас все используют модули, поэтому тут особо другого нечего поставить.

Скрипты для запуска, как я и сказал, и для других действий, и зависимости.

То есть зависимости – это для разработки, и будут еще обычные зависимости.

Зависимости – это библиотеки в проекте, которые мы будем использовать.

Дальше, файл readme, опять же, для описания, что в нашем проекте есть для гитхаба и вообще для гита в целом.

TS-конфиг, и опять же, видишь, есть TS-конфиг такой, но он как бы ссылается на TS-конфиг, который есть уже внутри Naxt по умолчанию, можете его посмотреть.

Кстати, он настроен достаточно хорошо, настроен прям круто, мне понравилось, я с ним поработал, кайфанул.

Вот, правда, не понял, зачем они дублируют, собственно, и через собаку, и через тильду, если можно что-то одно выбрать.

Ну, окей, это дело как бы, дело вью, мы не спорим.

Ну и Yarnlock файл, да, это те же самые зависимости, только здесь содержится файл с конкретными версиями всех зависимостей.

Давайте отблизим обратно, чтобы вам было не так близко.

Чуть-чуть я подвину, чтобы вам было так покомфортнее.

Вот так примерно.

И я предлагаю взглянуть тогда быстренько на документацию.

Я вам покажу, чтобы вы просто понимали, что мы будем писать.

Я вам покажу, как вообще обустроена здесь структура.

Вкратце, да, опять же, вкратце.

Касаемо роутинга.

Переходим в роутинг.

Вот наш pages.

Создаем папку pages, в ней создаем файлики.

Если нужна динамика, пишем квадратные скобки.

Опять же, дальше мы об этом поговорим.

Я подробно сейчас останавливаться не буду.

Мы потом на практике эту историю разберем в этом ролике.

Касаемо SEO-оптимизации.

Тоже вот информация, как там title назначать и так далее.

Кстати, usehead, да, используется здесь, а я использовал другое, я использовал use-seo-meta.

А я даже не знаю, что лучше.

Типа здесь говорят, лучше поддержка TypeScript, а короче, я не знаю, то есть они как будто одинаковые.

Потом, если мы перейдем во views, вот наш app-view, допустим, и вот наши компоненты, да, важно в папку компонентов вложить компоненты, тогда у них будет, опять же, без импорта можно будет их переиспользовать.

Пейджесы и так далее, ну, короче, я думаю, что это все, ну и layout тоже без них никуда.

Далее у нас есть вот homepage, да, я сделал скриншот, чтобы нам было понять, что делать с ним.

Вот, я его сразу закреплю сейчас.

Только хочу открыть файлик какого-нибудь здесь, чтобы его закрепить аккуратненько.

Вот так, пин.

Это тоже нужно пин, чтобы оно никуда не пропало случайно.

Это чуть опустим, я думаю, до этого уровня.

Так, и это тоже чуть подвинем вот сюда.

Я думаю, так достаточно будет.

Так, хорошо.

Давайте сначала будем по очереди всю эту историю разбирать.

По очереди постепенно.

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

Давайте его создадим.

Для этого есть специальная папочка, называется Layouts.

Вот только так это будет работать.

И файлик назовем Default View, то есть дефолтный лейаут.

Это, опять же, важное именование, по-другому писать нельзя.

Что такое лейаут?

Лейаут — это вот общий каркас, который используется на каждой странице сайта.

Ну, там, где вы захотите использовать.

И для этого NUX очень крутую концепцию придумали.

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

Это удобная концепция, она отличается от того, что есть в React, в Next.js, да,

Она, мне кажется, чуть более даже удобна.

Хотя, опять же, я привык больше к Next-овской истории.

Но, тем не менее, про что я говорю.

То есть мы здесь можем использовать сразу большое количество лейаутов в проекте.

К примеру, для публичной части один лейаут, для части, где там гуляют обычные гости, для части администратора другой лейаут и для части...

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

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

Это очень круто и очень удобно.

У нас будет в проекте один layout, потому что у нас в целом нет какой-то гостевой части, у нас просто разработка CRM-системы, а это уже и есть админ-панель, скажем так.

Давайте сделаем, как у нас во Vue.js вообще обстоят дела с компонентами, как это все устроено.

Значит, во-первых, по поводу экстеншенов сразу скажу, что я себе поставил по вьюшке.

Я много чего пробовал и, опять же, идеального концепции какой-то не достиг.

Почему так?

Потому что все равно вью из-за того, что он... расширение файла вью, то точка вью, да, из-за того, что...

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

Бывают баги, при которых приходится перезагружать все экстеншены.

Я вас потом научу, как это делать, чтобы вы это могли быстро делать.

Если работать в AppStorm, то в AppStorm проблем не будет, там все это дело достаточно умно работает.

Но, тем не менее, я столкнулся с такой проблемой Короче, важно поставить вот TypeScript View плагин для интеграции с TypeScript Здесь, смотрите, мы просто ставим все, что от разработчика Volar Вот, Volar, да, то есть это Vue.js официальные разработчики И также ставим вот эту штуку, вот Там, на самом деле, есть целый комплект, можно написать Vue.Volar, допустим Вот, и тут где-то можно, наверное, открыть Вот можно такую штуку открыть, да, и посмотреть, что вам предлагают поставить Здесь из основного только две эти штучки Все остальное можете не ставить, это, ну, не актуально, скажем так

Все, поставили.

Идем дальше.

Значит, в новом Vue, я буду говорить Vue, но вы имейте в виду, что я пишу на Nux.

В принципе, концепция одна и та же.

У нас как идет компонент, как выглядит.

Если в React мы используем там Export Function, допустим, компонент является функцией, здесь ситуация немножко другая.

Здесь разработчики Vue решили добавить очень много магии, да, и у нас все происходит под капотом, мы не понимаем, что там происходит на самом деле.

И это, конечно, огромный минус в Vue, то есть когда будем писать большие проекты, нам сложно будет их тестировать.

Но, тем не менее, для новичков это суперкрутой инструмент.

Почему Vue нравится новичкам?

Потому что новички могут легко себя здесь найти и писать в Vue приложение спокойно, потому что это реально легко делается и легче, чем в React.

Но как только уровень достигает сложного, да, я вам сегодня покажу на примере этого проекта, уже есть проблемы определенные.

С этим нужно мириться, скажем так.

Значит, template.

Здесь у нас в таком якобы теге будет описываться HTML-код в перемешку с небольшим компилятором, где мы там будем динамичные перемены вставлять и так далее.

Внизу у нас идет style.

Тут в стиле обычно пишем scope для того, чтобы стили относились только к этому компоненту, чтобы они не работали вовне.

А вверху мы делаем скрипт.

Тут все скрипты наши, то есть весь наш TypeScript-код.

Чтобы он был TypeScript-ом, именно пишем lang.ts.

Это обязательно.

lang.ts.

Length.ts.

И также указываем setup.

Да, это тоже важно для Composition API, нового как раз keyview.

Без этого смысла делать нет.

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

На самом деле, новая концепция view мне нравится больше.

Она чем-то становится похожа на React, чем-то удобнее.

Поэтому всем рекомендую именно использовать view3, а не view2.

Значит, что мы здесь можем попробовать?

Допустим, давайте консолок я напишу базовым.

Значит, напишу loaded.

Здесь напишем базовую структуру какую-нибудь.

Допустим, div.

Так, div.

Давайте сразу, наверное, сделаем какую-то структуру более-менее адекватную.

Давай сразу сделаем секцию, какую-нибудь секцию, да, которую мы сейчас постельно опишем.

И как нам обозначить вот этот layout, да, я об этом вам сказал, да, и вы, наверное, спрашиваете, Макс, а как нам обозначить, вот в каком месте будет располагаться сам children, то есть в React называется children, то есть ребенок, как сказать, не ребенок, а вот то самое, что мы оборачиваем.

То есть условно объясню, вот наш макет справа, видите, да, у нас есть sidebar, к примеру,

Это и будет являться нашим лейаутом.

Сайтбар будет на каждой странице сайта.

А вот то, что вот с правой части, да, это является, во вью называется слот.

Является слотом.

И вот его нужно поместить в определенное место нашего лейаута, чтобы он там постоянно слежал.

Как это отобразить?

Так и отображаем.

Тег слот называется.

Вот, тег слот.

Все.

Значит, в это место будет ложиться все, что мы будем оборачивать.

Вот, допустим, эту штуку будем ложить сюда.

Сохраним, к примеру, да, и я думаю, что можно запустить проект.

Пишем yarn dev.

Так, давай еще раз.

Yarn dev.

Только не забудем использовать сам наш layout, перейдем в app view, здесь укажем layout, next layout непосредственно, next layout.

Здесь, чтобы использовать, как я и говорил, другую вариацию layout, можно потом сделать вот таким образом и указать там name какой-то условно.

Вот, ну мы будем использовать один layout.

Идем сюда, обновляемся, вот наш next замечательный.

Вот наш NAX замечательно заработал.

Теперь идем сюда, inspect.

И можем посмотреть в консоли, что у нас выполнился loaded, как видите.

Так, давай чуть консоль покрупнее попробую сделать, чтобы вам было все видно.

Вот, наверное, таким образом.

Да, чтобы за мной еще было вам видно.

Вот, loaded, видишь, запущено.

Значит, когда у нас отрабатывает вот эта штука?

То есть важно понимать, что этот скрипт, если вы пишете код внутри него, он отрабатывает именно при создании компонента.

Именно при создании, ключевое слово при создании.

То есть не при измене чего-то, а именно при создании.

То есть когда вы грузитесь, создался компонент, у вас выполняется все, что здесь находится.

Это нужно понимать для общей концепции.

Дальше, как я сказал, у Naxx очень много крутых приколюх, которые, честно вам признаюсь, я так и не использовал.

Я не понял, для чего они...

круто все выглядит, красивенько подсвечивается, это, короче, режим разработки, давайте его откроем непосредственно, вот, нажимаем get start, давайте это даже свернем, наверное, так, анонимус не буду отправлять, и вот такая штука, да, то есть здесь есть и всякие там, дерево можно посмотреть, на экстраутинг можно посмотреть,

Можно посмотреть также у нас и всякие другие штучки.

У нас пока просто роутинга нет, поэтому она не показалась.

Есть все наши компоненты, есть там все наши функции, библиотеки.

Короче, много всего.

Но честно вам признаюсь, я ни разу этим не пользовался.

Вот честно.

Я не знаю.

Напишите в комментах разработчики Vue.js и Nuxt.

Вы эту штуку вообще пользуетесь?

Я не знаю.

Наверное, да, пользуетесь.

Но я в ней не нашел никакого такого прикола.

То, чего прям нету, знаешь, в обычной разработке в хроме, в обычном браузере.

Ну, короче, я не по эту штуку, поэтому ее закрываю просто и больше ее не хочу использовать.

Так.

Кстати, можно, наверное, это выключить, да, я думаю, чтобы нас это не беспокоило.

Давай еще чуть-чуть откроем, верну назад, чтобы вам было все видно.

Чуть-чуть опустимся.

Ну, вот до такого уровня, я думаю.

Так, возвращаемся назад.

Давайте, кстати, перейдем в Next и попробуем отключить.

Наверное, вот сюда пишем false, и мы отключаем.

Да, по идее, должно отключиться.

Все, видите, отключилось.

Вот так гораздо удобнее стало нам.

Так, тут сразу сообщение о том, что suspense пока что экспериментальная штука, да, и будьте в этом плане аккуратны.

Что такое suspense?

Давайте сразу объясню.

Мы его будем использовать редко.

По-моему, мы вообще в этом проекте не будем использовать.

Но для тех, кто, допустим, я обещал вам все рассказывать, поэтому расскажу, что такое suspense.

Suspense — это когда мы...

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

То есть, к примеру, у нас грузится такой компонент внутри, да, а мы ставим загрузчик на весь сайт.

И вот, когда мы ставим загрузчик на весь сайт, и чтобы он...

понял, что у нас идет загрузка нашего компонента, мы используем suspense, который перебрасывает loading как раз-таки к нашей загрузке.

Если простым человеческим языком, это именно так работает.

Опять же, без каких-то сложных терминов.

Я стараюсь максимально легко доносить, я сам теорию ненавижу, поэтому, ребята, я буду доносить своими словами все, что знаю.

Окей, с этим понятно, я думаю.

Так, закрываем.

Дальше.

С этим, я думаю, тоже разобрались, предварительно познакомились.

Касаемо самой секции.

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

Давайте накинем базовый стиль, то есть нам нужно сделать, чтобы левая часть была...

Такого размера, правая часть была такого размера.

И внимание, кстати, в консольке тоже на сервере отобразец, как видите, да, вот чуть справа от меня.

Давай чуть-чуть еще подвинем вот таким образом.

Отобразец, как видите, в консольке loaded, то есть выполняется и на сервере, и на клиенте.

Вот, но спойлер, мы наше приложение будем делать только на клиентской части.

Я не вижу смысла делать это приложение на серверной части, потому что мы разрабатываем с вами все-таки админ-панель, в которой не так важен сервер, и я решил даже туда не обращаться, честно говоря, потому что у нас авторизация, app-write не умеет работать с сервером адекватно, поэтому я не вижу смысла сейчас вот этим заниматься.

И тем более хотел сделать новичков именно для джуниор-уровня, чтобы это как-то сильно вас не перегружать.

Ну, как бы сделать здесь сервак особой проблемой составляет.

И в стилях я напишу .grid и напишу grid

Template columns.

И укажу шаблон нашей колонки.

Смотрите, у нас будут две колонки.

Это левая и правая.

Первая будет колонка «Одна фракция», а вторая будет «Шесть фракций».

Я легко это высчитал на основе первой колонки.

Это будет назначаться к секции «Класс».

grid да и теперь у нас к этой секции будет назначаться вот этот класс этот класс нигде больше не будет использоваться кроме этого компонента что мы указали атрибут скопт ну и также укажу style минимальная высота наше приложение будет 100 ведь то есть ширина экраны высота экрана это будет минимальная высота нашего приложения сохраним обновим что мы увидим пока что наверно ничего давай посмотрим еще раз и

значит мы видим вот наш grid замечательно да и мы пока что видим только один элемент да по этой причине у нас он растягивается на full потому что вот как видишь у нас ошибка потому что элемента нет да ну и не только поэтому у нас просто нет даже понимаешь что является кредон поэтому не забудем здесь еще написать значит display grid display grid да и вот здесь добавим еще один div пока что такой временный да это будет наш типа sidebar sidebar

Сохраним, как видите, вот сайдбар слева получился, а это справа.

Вот такая концепция у нас вышла.

И теперь это является лояутом, можно использовать в любых других страницах.

С этим, я думаю, понятно.

Давайте постепенно что-нибудь делать уже будем.

Я думаю, что начнем по сайдбару, наверное, сделаем сайдбарчик.

Потом пойдем постепенненько.

Давайте для сайтбара создадим components, new folder components.

Это важно на именовании папки, менять его нельзя.

И все, что мы будем делать внутри компонента, потом можно переиспользовать, как вот эти истории, без импортов.

То есть мы просто его вызываем, и не нужно ничего импортировать.

Он и так сам будет подтягивать.

Это очень крутая тема, именно Nux.js.

Давай мы еще как поступим, чтобы компонент у нас особо, опять же...

Давайте я сейчас объясню, что я хочу сделать.

Напишу лучше layout здесь, layout, чтобы было понятно, что эти компоненты относятся к layout именно.

И внутри него создадим непосредственно файлик sidebar.view.js, да, sidebar view, template.

У нас, кстати, сниппетов нет, давайте поставим сниппеты, view snippet, потому что без сниппетов тяжеловато немного, вьюшных, давайте поставим.

Так, view, view with code snippet, вот такая штука есть у нас, да.

Так, а есть view 3, да.

Наверное, view 3 поставлю, да, все-таки.

Давай их поставлю.

Я, по-моему, их уже пробовал.

Вот эти я не пробовал.

Не знаю, насколько они актуальны вообще, эти view.

Так, что написано?

View 3, view 2.

Ну, ладно.

Давай все-таки вот эти поставим.

Они уже проверены.

Так, теперь пишем здесь.

Хотя, может быть, те были лучше.

Не знаю.

Пишите в комментах, прав я был или нет.

Короче, разворачиваем просто div.

Вот таким образом все развернулось красивенько.

Так, скрипт тоже пока развернем, мы его пока юзать не будем.

Так, неправильная история.

Вы сейчас как раз видели старый моментик.

И вот как видите у нас, да, вот уже наглядный пример.

То есть смотри, я пишу скрипт, я пишу лэнг, да, и он его уже не подтягивает, уже не подтягивает.

То есть если я навожу, он не поймет, что я имею в виду вообще в целом.

И вот это называется баг в UGS.

Вот пишем setup, видишь, он его тоже не понимает.

Чтобы это исправить, этот баг...

Видишь, он даже до сих пор не подсвечивается.

Он не понимает, что происходит.

Для этого мы нажимаем Command-Shift-P, указываем Restart Extension Host.

Вам придется этим пользоваться крайне часто.

Пишем просто Restart Extension Host и перезагружаем все экстеншены в нашем виз-коде.

Может, у меня вот такой баг.

Ну, вот реально, в UGS часто багуются.

Нажимаем сюда.

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

Наводим.

Как видишь, баг исправился.

Пожалуйста, все работает.

Вот такие баги часто происходят во Vue.js, и это, конечно, не суперкомфортно.

Знаешь, в React никогда такого нет, что есть проблемы с TypeScript.

Вообще никогда такого нет.

А вы дальше посмотрите, что проблемы будут также и с TypeScript непосредственно.

Так, значит, сайдбар.

Сайдбарчик мы пока что в скрипте не будем описывать.

Опять же, к нему вернемся чуть попозже.

Здесь что мы хотим описать?

Так, это сайдбар, я напишу «aside», да, HTML5 тег «aside».

Здесь будет наш сайт-бар непосредственно располагаться.

Нам нужно Tailwind поставить для удобства написания классов, чтобы мы их быстро писали.

Опять же, многие не понимают прикол Tailwind.

Объяснем, почему мы используем Tailwind в большинстве проектов.

Потому что Tailwind вам позволяет максимально быстро писать приложения.

А что важно новичку на старте и мне, что важно при написании больших проектов, это максимально укоротить процесс именно верстки и стилизации сайта.

Потому что когда вы переходите с уровня верстки на тренни и выше...

Вам уже верстка не так важна, вы хотите ее максимально избежать, потому что вы уже в ней прокачались, вы уже ее знаете.

Опять же, важно ее реально знать на хорошем уровне.

И потом вы уже можете просто брать и переходить дальше.

И вам не хочется верстать каждый раз одно и то же.

Поэтому Tailwind позволяет вам в 5 раз сократить разработку.

Просто потому что есть готовые классы, которые вам позволяют это сделать в разы легче.

Поэтому я крайне рекомендую Tailwind изучить каждому, чтобы просто клепать постоянно пэд-проекты и повышать свой скилл за счет написания функционала, а не верстки.

Дальше.

У нас, помните, вот здесь идет вот такая логотипчик, наверное, замечательный.

Напишем поэтому «Next link».

То есть это то, как у нас пишутся ссылки в «Наксте».

«Next link».

Здесь указываем, по-моему, здесь идет «to» у нас в «Наксте».

И вести на главную страничку, ту слэш.

И сам логотипчик, next image я буду использовать, вот такой тег, да, уникальный.

Можно просто обычный имидж, но next image, как и в next, есть свой тоже тег, точнее, компонент, который позволяет вам кэшировать ваши картинки.

Вот, опять же, с кэшированием будьте аккуратны в Next, у меня была такая проблема, когда вы меняете картинку, вам приходится очищать кэш, потому что он сам его не очищает, и приходится переобновлять кэш вручную.

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

Так, указываем source, он будет располагаться как раз таки в нашей папочке, logo.png.

У нас, кстати, не PNG, а SVG будет.

Ну и дальше будет Alt вот такой и ширина 100 пикселей.

Давайте добавим нашу картинку.

Вот, я перенес папочку Public, вот Loader и лого SVG.

Опять же, так как мы полностью проект весь даем вам бесплатно по ссылке в описании, можете скачивать.

Ну, я в телеге выложу, чтобы хоть как какие-то плюсы получить, да, чтобы вы хотя бы на телегу подписались, ребят, чтобы... Короче, чтобы мы с этого хоть что-то получили.

Вот, я надеюсь, вам не сложно подписку на телегу оформить, а нам будет приятно.

Вот.

И там оттуда можете взять картинки.

Вот лоадер замечательный и вот лого.

Лоадер нам потребуется опять же дальше в проекте.

Указ лого SVG.

Сохраняем.

Закрываем.

Ну пока что вот так и оставлю.

Примитивно максимально.

И идем в наш лояут и его инициализируем.

Лояут.

Дефолт.

Идем сюда.

И сюда добавляем наш замечательный сайдбар.

Сайдбар.

Сохраняем.

И как видите, я неправильно писал.

Я по привычке пишу в формате React и хочу там импортировать.

Мы можем так сделать, взять и импортировать, и все будет работать.

Вот так, sidebar view, и все будет работать.

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

Убираем.

Вместо этого пишем layout-sidebar.

Как это строится название?

На основе папки.

То есть у нас есть components, она не читается.

Layout и sidebar.

И это работает только для тех компонентов, которые есть в папке components.

Для других оно работать не будет.

Layout, sidebar.

Сохраняем.

Идем сюда.

Ошибочка у нас оформляется, потому что next-image нужно установить сначала.

Давайте перейдем в документацию и его поставим.

next-image.

Переходим сюда.

Идем сюда.

Вот таким образом выполняем команду next-image.

Давайте get started нажму, через yarn поставлю себе Идем сюда, открываем терминальчик еще один yarn add next image Ставим next image и в качестве модуля его подключаем Про что я вам говорил, что все работает в next конфиге Первую строчку удалю, она мне нужна, и вот здесь я буду подключать next image Здесь будет прям много модулей у нас в будущем Давайте сразу, пока мы пошли сюда, поставим tailwind next tailwind, далеко ходить не будем

Так, сюда идем, get started.

Вот чем мне реально Nux нравится, у них прям реально крутая документация.

Видишь, крутая стилистика, мне прям очень нравится.

Так, берем.

Может, я один от этого кайфую, не знаю.

Может быть, вам как бы и окей.

Но мне прям реально нравится, я прям кайфую.

Так, ставим.

Дальше мы берем вот это и копируем.

Уже массив не нужен, потому что у нас уже есть, и просто как через запятую указываем еще одну историю.

Опять же, Tailwind объясняет, для чего он нам нужен, для удобства, опять же.

Так, видишь, TypeScript, опять же, вылетает, будьте аккуратны, вот такое часто происходит в проекте Vue.js.

Может быть, у меня настройка неправильная, редактор, ребят, не знаю.

Пишите в комментах, опять же, мне интересно почитать ваше мнение, вот именно про разработчиков, которые на Vue часто пишут, на Nux, есть ли у вас проблемы с TypeScript или нет.

Потому что у меня ощущение, что изначально Vue был сделан под обычный JS, не под TypeScript, и из-за этого все равно еще тяжело идет портация.

Особенно с всякими Legacy плагинами и экстеншнами, то есть со старыми, я имею в виду.

Так, по идее он сам их нам сгенерирует, насколько я понимаю, да?

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

Но накинем немножко стилей.

Перейдем сюда обратно.

Так, давай сейчас перезапустим проект.

По идее должны файлы создаться сами автоматически.

Выключим и заново запустим.

Вот, using Tailwind.

Вроде бы запустило все отлично.

Вроде работает.

Так, Tailwind конфиг у нас нет, как я вижу.

Да, его нужно создать самим.

Давайте создадим его.

Так, перейдем сюда.

И, собственно, сгенерируем эту штуку.

Так, npx tailwind init.

Но, опять же, это временное решение.

Мы с вами дальше поставим, как я и говорил, UI-библиотечку, которую, опять же, вы просили, зрители, да, это Shade, давайте посмотрим, как она называется, Shade, вот, Shade CN View.

Мы ее поставим чуть дальше, вот, и она под капотом использует у себя Tailwind.

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

То есть ее уникальность в том, что она как бы делает компоненты с помощью команды, но мы их можем менять.

То есть это не как, знаешь, берем UI-библиотеку, и нам приходится все время перекастомизировать.

Такого нету, да Здесь компоненты, она вам готовит просто готовые компоненты А вы уже их сами под себя меняете Это невероятно удобная штука Но она, конечно, такая, знаешь Много чего делать лишнего, на мой взгляд И я все-таки все равно любитель чистой структуры Поэтому я привык делать все сам Но, опять же, скорость она реально добавляет Вот разработки

Так, с этим мы сделали, я думаю.

Да, теперь давайте попробуем стили накинуть небольшие.

Я, опять же, стили буду некоторые копировать.

Тут объяснение особо не нужно, я думаю.

Все, я думаю, понятно.

Да, паддинги, background sidebar, который у нас пока что нет, к сожалению.

Да, мы потом добавим наши цвета чуть позже.

Высоту добавляем, ширину и relative обязательно.

Дальше добавляем небольшие стили для самой ссылочки.

Небольшой просто маржин делаем и блок.

И, в принципе, для картинки делаем MX Auto, потому что нам важно, чтобы она выровнялась по центру.

Обновляемся.

Вот она по центру стоит.

Все прекрасно отработало.

Как видите, стиль работает.

Если мы зайдем сюда, то мы увидим, вот наши стили класса, и они прекрасно отрабатывают в правой части.

Так, напишу здесь тудушку.

Нам нужно добавить 6CN.

Давайте мы его скопируем.

Так, теперь перейдем, собственно, в наш браузер.

Откроем этот 6CN view.

И давайте, собственно, его установим.

Get started.

Собственно, объясняю еще раз концепцию.

Не помню, смогу донести ее или нет.

Давайте еще раз объясню.

На всякий случай, опять же, хочу, чтобы вы точно все поняли.

Смотрите, значит, beautifully designed components.

that you can copy and paste into your apps.

Accessible, customizable, open source.

Что это по-русски, если говорить?

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

А есть условно вот такая штука, которая позволяет нам вроде бы и брать готовые компоненты,

И при этом это не совсем готовый компонент, потому что вы их можете редактировать.

То есть обычно как?

Допустим, берем Material UI, то, что вы все знаете.

Берете Material UI.

Дальше.

Вам нужно кастомизировать под ваш дизайн какую-то историю.

Что вам нужно сделать?

Вам приходится перезаписывать классы и перекастомизировать.

То есть перезаписывать уже старый класс.

Это не суперудобно, это конфликты вызывает.

Ну, короче, это максимально некомфортно.

Здесь же история такая, что он вам генерирует компонент базовый.

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

И во Vue.js они так давно появились.

Но вот вы просили, я сейчас использую, чтобы вам была такая новая практика, скажем так.

Будем стараться с вами применять новые технологии.

Вот.

Это, я думаю, понятный момент.

Теперь мы переходим с вами в Get Started.

Get Started, да.

И опускаемся ниже.

У нас есть вот Installation.

Есть вот такие штуки Nuxt, Vite, Astra и Laravel.

Давайте возьмем Nuxt и поставим наш замечательный Nuxt.

Значит, возьмем эту строчку замечательную.

Идем сюда и вставляем ее.

NPX.

Так, подожди, подожди, стой, стой, стой, стой, стой, стой, стой, что ты делаешь?

Блин, я совершенно эту команду скопировал, ребят.

Так, надеюсь, ничего не поломало здесь.

MyApp, вот это лишнее, удаляем MyApp сразу же.

Да, нам повезло, все в рамках разумного получилось.

Он просто создал еще одну папку, все отлично.

Так, TypeScript у нас уже есть, это у нас тоже уже есть.

Вот этой штуки у нас нету, давайте ее скопируем.

ShadCNNax, давайте, yarn.ddd.

Так, минус D забыли, да?

Минус D. ShadCN Next.

Поставили.

Дальше.

Вот такая штука.

Конфигурируем.

Значит, берем ShadCN.

Я вот эту штуку написать не... А, ну хотя это нам нужно.

Сейчас объясню для чего.

Давайте скопируем пока что.

Перейдем в наш замечательный.

Как видите, он просит, чтобы у нас Tailwind уже стоял.

Мы его буквально вот пару минут назад с вами совместно поставили.

Через запятую указываем еще один модуль, который будет называться ShadCN Next.

Вот такой модуль замечательный.

Его также устанавливаем.

Теперь ставим очень интересную штуку.

Давайте я объясню, что она делает.

И дальше можно запускать уже эту штуку.

Значит, смотрите, шатсен, она позволяет, тут мы указываем префикс, да, для всех компонентов, допустим.

Я думаю, префикс мы делать не будем, наверное, мне кажется, что он не нужен, да, префикс не нужен.

Что такое префикс, да?

Условно, у нас будет компонент, с вами input, допустим, да, вот input, да, вот такой компонент.

И, как вы знаете, наш замечательный Nuxt позволяет нам его импортировать.

Я про это уже говорил не так давно.

И то есть мы с вами можем делать, допустим, UI input, к примеру, да?

А префикс это то, что мы добавляем дополнительно еще спереди.

Допустим, вот тут будет префикс.

Вот это тот самый префикс, который будет ко всем компонентам прибавлен.

Мне префикс не нужен, мне хватит просто вот этого дефолтной истории, поэтому я это уберу.

Это мы тоже убираем.

Директория, где будут располагаться все наши UI components, UI.

Так, shad-scene я оставляю здесь, просто сохраняем.

Дальше, все, что мы делаем, это инициализируем наш SLI.

Будьте внимательны, ребят, когда вы инициализируете, у вас перетрется файл tailwind-config и файл tailwind-css.

Он у нас создался или нет, давайте посмотрим.

По-моему, он автоматически так и не подсоздался, у нас этот файлик.

Да, короче, если у вас есть файл tailwind.css, то он его тоже подотрет.

То есть он его перезаменит на свои стили.

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

Будьте внимательны.

Итак, запускаем npx-shad-cn-view-latest и init.

Запускаем.

Ожидаем немножечко.

Would you like to use TypeScript?

Конечно, хотим TypeScript использовать.

Выбираем next.

Либо дефолтно, либо New York выбираем стиль.

Тут, на самом деле, нужно подумать.

Нужно подумать, потому что...

Вот этот проект справа, который вы видите, да, я его в принципе делал на дефолтной истории.

То есть вы можете посмотреть различия стилей вот здесь.

Допустим, откроем какой-нибудь условно input.

Вот здесь все компоненты.

Откроем input и видим, да, вот наш email замечательно.

Это дефолтный, как видите, да.

А вот New York.

Я выберу Нью-Йорк.

Вот, он становится просто более минималистичным.

И, на мой взгляд, я, наверное, себе в этот раз выберу Нью-Йорк именно для записи этого видео.

То есть на демке был дефолт, я выберу Нью-Йорк, потому что хочу более минималистично, потому что мне потом приходилось подравнивать.

Я возьму Нью-Йорк вариант.

Тут уже тоже интересный моментик.

Давайте раздвинем консольку.

Нужно выбрать, какой будет у нас базовый цвет.

Вот этот самый тоже такой момент интересный, но нам он не сильно важен.

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

Вы можете открыть Tailwind, вот здесь у нас есть Tailwind Viewer, вот он открывается, и посмотреть, что такое слейд, вот такие тона, видите, они такие немножко синево отдают, мне кажется, или, может быть, я дальтоник, ну, короче, понял, да, есть грей вот так.

Вот такой цвет.

Есть слейд, вот такой, да.

Ну, кстати, слейд, наиболее нам подходит слейд, мне кажется.

Вот я визуально смотрю, цинк нам не подходит точно, нейчурал тоже не подходит.

Я думаю, цинк точно под наш оттенок больше подходит.

Он, видишь, он такой немножечко как будто синевастенький.

Да, нам слейд больше всех подойдет.

Слейд как переводится по-английски?

Давайте изучать вместе.

Слейд у нас это, так, только не японский, шифер.

Нифига себе.

Это шиферный цвет, прикиньте.

Я, наверное, возьму слейд, пожалуй.

Вы потом это можете поменять легко, поэтому не парьтесь особо.

И где будет располагаться ваш tailwind.css файл?

Что такое tailwind.css файл?

Это файл, в котором будут базовые стили tailwind и вся база вашего приложения, рутовский css файл, в который вы можете это менять в потенциале.

Я оставлю дефолтную историю, assets, css, tailwind.css.

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

Нажимаем Enter, сохраняем.

Ты хочешь использовать css variables для цветов?

Вот это тоже интересный момент для цветов CSS Variables.

Тут нужно подумать хорошенько.

Объясню почему.

Потому что когда я это делал, этот проект, я за это немножко поплатился.

Если мы используем CSS Variables, он их оборачивает в дополнительный, так называемый, этот...

Как сказать?

HSL.

Я не помню, как он переводится.

Короче, в такой формат цвета непосредственно.

И это супер неудобно.

То есть, когда вам нужно пипеткой взять какой-то цвет условный, вам приходится этим всем заниматься.

Но я все равно оставлю Variables, потому что, опять же, у меня уже есть готовый проект, и я оттуда буду копировать.

Так что оставим Variables.

Но будьте внимательны, вам придется, скорее всего, переделать.

Вам будут просить HSL получить.

Вам нужно будет на HEX формат переделать.

Я вам покажу, как это сделать дальше.

Нажмем «Yes».

Located.tlwinconfig у нас, да, все верно, по дефолту.

Так, alias мы оставим так, как есть, ничего особо конфигурировать не будем.

Utils я бы оставил просто slash utils, потому что lib мне сильно нравится, я его так оставлю.

Так, дальше.

Написать конфигурационный компонент JSON-файл.

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

Вот, чтобы потом в потенциале могли подредактировать.

Конечно, ставим yes.

Все, установка пошла у нас непосредственно.

Тут некоторые ошибки будут, это нормальная история.

Все у нас установится, обновится, и мы с вами получим готовые компоненты.

Так, давайте перейдем пока назад.

Это закроем.

И начнем с того, что поставим первый компонент, вот опустимся ниже, вот допустим, add button, добавляем кнопку непосредственно, да, вот так, возвращаемся Все, success, успешно все установлено, чистим консольку, и давайте посмотрим, что у нас получилось Он нам создал по идее, пока что ничего не создал еще, да, но как минимум ассетсы появились, вот про что я вам говорил, да, это дефолтные всякие стили Вот будьте внимательны, видите, что происходит, вот этот формат HSL

да, как его поменять, если вы, допустим, хотите установить формат hex, то есть, условно, у нас пипетка, я использую программу color slarp на маке, и вы, допустим, взяли пипетку, какой-то цвет, условно, да, и вы хотите его добавить в качестве перемены, то есть, чтобы везде бэкграунд был такого цвета, работа так не будет, чтобы оно работало, вы меняете здесь, затем идете в tailwind.config.js файлик, да, опускаетесь ниже, тут все, что он сам настроил, как я и говорил, он все перезаписывает, и вот здесь

Вы убираете HSL там, где он не нужен, и просто оставляете вар, чтобы он просто подхватывал сам цвет.

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

И, кстати, он добавляет анимацию, вот что интересно, и я в своей практике, честно говоря, Tailwind Animate ни разу не использовал, вот будет на заметку на будущие проекты.

Я не знал, что он есть, честно, ни разу его не использовал.

Надо будет поюзать.

Реально прикольная тема.

Так, к этому мы сейчас вернемся.

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

Это темные тона непосредственно.

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

И базовые какие-то моментики.

Мы тут шрифт себе поставим другой, чтобы, опять же, покрасивее все было.

Но этим займемся чуть попозже.

Давайте сделаем первый наш компонент и закончим с этой библиотечкой уже.

Значит, давайте сделаем компонент.

Так, давайте вернемся назад, копируем строчку.

Значит, npx-shade-cn-view-latest-dd-button.

Добавляем кнопочку.

Ожидаем генерацию.

Он сам нам генерирует.

И сейчас вы посмотрите магию определенную.

Напишет done.

Теперь мы смотрим, собственно, компонент UI.

И вот наш button.

И обратите внимание, что у вас теперь button вы будете импортировать прямо из своего же проекта.

Это настолько круто.

Я в React еще эту штуку не пробовал, честно вам признаюсь.

Но буду пробовать обязательно в следующих проектах, потому что реально крутая тема.

И вы видите, вот у нас e-button.

Помните, да, я вам говорил по структуре.

То есть, когда вы пишете без импортов название компонента, его можно не импортировать.

Имейте в виду, если повторяется название с файлом, то он игнорируется.

Это важно понимать.

То есть, он может делать структуру определенную.

И тем самым, чтобы вызвать наш button, нам придется как вызвать?

Давайте для теста вызовем.

Пишем UI button.

Button вот таким образом.

Наведем сюда.

Как видим, unknown.

Надеюсь, что... Точнее, правильно читается не unknown, а unknown.

Надеюсь, что у нас ошибка пока что временная.

Can't find utils.

У нас какие-то некоторые проблемы в проекте, я вижу.

Никаких utils почему-то мы не наблюдали.

Не понимаю, где они вообще, utils.

А, utils аж здесь у нас.

А-а-а.

Он utils не туда положил, куда я думал, что он их положит.

Помните, я там поменял немножечко, да, при установке, так что имейте в виду, что будет такое, я думал, что папку создаст нам Ну ладно, раз ты не хочешь, давай сами создадим папку utils и в нее уже положим наш файлик Вот сюда, оп, не понял, что ты хочешь?

Так, вроде переместилось, вроде, давайте, компонент JSON перейдем сюда еще раз, смотрим, значит, utils, слэш, utils, вроде бы все нормально

Вот тут базовый Color, который я вам говорил.

Да, Base Color, можно тоже поменять его без проблем.

CSS Variables там, ну и так далее.

Вы это все можете здесь спокойно менять без проблем.

Так.

Что теперь?

Теперь утилс, слэш, и, значит, у нас там в утилсах что идет?

Еще один утилс, да, давай так сделаем, утилс, утилс.

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

Так, посмотрим, вроде бы должно работать вот таким образом, сеанс смотрим, сеанс, все, вот так работает.

Чуть пришлось с бубном побегать, но здесь особо ничего мы не поменяли.

То есть понимаете, да, в чем прикол?

Он не видел путь, поэтому нам пришлось сделать таким образом.

Вот, и давайте рассмотрим, да, вернемся назад немножечко, рассмотрим наш компонент, из чего он вообще в целом состоит Мне кажется, кстати, шрифт крупноватый для вас, разве нет?

Что-то я как-то не подрассчитал немного Да, наверное, я обычно так, ребят, разрабатываю, вот, чуть-чуть вот так и сделаю Вот, чтобы у нас просто больше кода помещалось, сейчас покрупней шрифт немножко сделаем Вот, я думаю, так вам будет видно, просто чтобы у нас больше помещалось в экран, чтобы вам было комфортнее смотреть

Блин, надеюсь, все будет видно, я просто немного переживаю, но должно быть все прекрасно видно, зато комфортнее будет на компьютерах смотреть На телефоне сейчас можете приблизить, опять же, надеюсь, что все будет окей Так, с этим понятно, так, сайдбар окей, это окей, дальше Дальше, дальше, да, я хотел разобрать UI-баттон, вот видишь, наводим на него, опять же, unknown почему-то Хотя у нас есть UI, button, view, вот он располагается, вот наш индексный

Откуда он импортируется непосредственно экспорта идет, вот экспорт default button, да и при этом вот у нас как раз таки наша кнопочка, то есть то, что нам загенерировала библиотека сама.

Здесь мы видим непосредственно пропсы принимаются, это у нас есть вариант кнопки, есть размер кнопки, есть как, что эта кнопка будет.

И по умолчанию «WishButton» эта кнопка.

И, собственно, вот дальше есть такие классы определенные и так далее.

То есть это все, я думаю, понятен момент.

То есть на выходе мы получаем обычный компонент.

Реально, обычный компонент, который можно спокойно менять.

Стили меняются вот здесь, потому что эта кнопка вариативная с помощью класса «Variants Authority», она работает, можно здесь менять спокойно.

Так что этот момент имейте в виду.

Так, с этим понятно То есть круто, да, что мы можем менять эту историю Давай сохраним, я проверю, работает или нет Потому что у меня какие-то странные подозрения Должна работать эта кнопка Но у меня какие-то реально подозрения, что она не работает Давайте я перезагружу на всякий случай view Чтобы он лишний раз не подругивался у нас И посмотрим, что получается вообще Так, обновимся

Так, обновилась.

Кнопку я не вижу пока что.

UI-баттон, конечно же, не отобразился.

Так, да, видишь, он ругается, что нету почему-то UI-баттона.

Так, странно.

Может быть, реально я префикс, наверное, забыл, да, мне кажется, префикс.

А если баттон просто ставлю?

Да, да, ребят, проблема в том, что все-таки префикс, видимо, нужен был.

Я вам неправильно немножко рассказал.

Я не знаю, почему я про это забыл, наверное.

Давайте напишем, что «send».

Вот, да, кнопка реально работает, но она без префикса, как видите.

Так что имейте в виду, вы это сами указывайте, я просто думал, что на основе структуры она будет работать, именно библиотека, но как видим, что она работает немножко иначе.

То есть если мы здесь посмотрим, не здесь, а вот именно в этом конфиге, то вот мы указываем, что где у нас префикс, и вот тут можно указать UI.

Сохраним, закроем, теперь если мы пишем UI...

то кнопочка наша работает прекрасно.

Вот, поэтому я бы рекомендовал все-таки указать с префиксом.

Мне кажется, так лучше на самом деле с префиксом.

Вот, оставим с префиксом.

Так, это понятно.

Вот такая кнопочка у нас получилась.

Крутая, минималистичная, комфортная и так далее.

Цвета мы сейчас с вами поменяем.

Ну, кстати, давайте к цветам вернемся.

Но перед цветами давайте вернемся к шрифту.

Я думаю, ну сейчас со стилями сразу все закончится, чтобы у нас все работало идеально.

Касаемо шрифта.

Значит, смотрите.

Для этого есть специальная тоже библиотека, называется NextFont.

Так, это мы оставим у себя, здесь напишем next font, next font google я напишу вот так, там специальность библиотечка у нас, по-моему, так она выглядит, get started, вот, поставим библиотечку сюда, устанавливаем, идем ниже, берем модуль,

Идем опять же в NuxConfig, устанавливаем наш модуль.

Это библиотека для подгрузки шрифтов непосредственно.

Так, сюда вставляем быстренько, вот таким образом, сохраняем.

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

Я это скопирую и вам объясню.

Вот таким образом мы копируем.

Так, возвращаемся назад.

Вот эту историю заменяем.

То есть мы берем наш шрифт и его настройки определенные.

Я буду использовать шрифт Lata.

Все шрифты можете взять из Google Fonts.

Вот касаемо шрифтов, в Next сделано гораздо лучше в React, потому что там ты можешь автокомплитом пользоваться, работать максимально круто и быстро.

Здесь не так комфортно, вам придется отдельно искать сам шрифт и его здесь указывать уже, но тем не менее работать с этим тоже можно.

Сохраним.

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

Сохраню.

Все, теперь у нас полностью все темное будет.

Но единственный моментик, я сразу же заменю, наверное, свои моменты.

Сейчас сюда фон фэмили поставим.

Фон фэмили у нас будет называться Lato.

И запятая sans-serif.

В случае, если он не будет работать.

Sans-serif.

Сохранимся.

Пойдем сюда.

Как видим, Lato у нас вроде бы инстализировался.

Да, он работает.

Но я вижу, что кнопочка перестала работать.

Кнопочка перестала работать, потому что мы, наверное, стили поменяли здесь.

Мне почему-то так показалось.

Что очень странно, на самом деле.

Background color.

War background.

222.

Ну, слушай, да, цвета есть.

Да, видимо, цвета не те все-таки поставились.

Ну, короче, не суть.

Это не так важно.

Опять же, у нас свои цвета будут, поэтому на это внимание обращаем.

Итак, что по своим цветам?

Я сейчас плавненько расскажу, что мы поменяли в нашей стилистике.

Из такого основного.

Я не все менял, сразу скажу.

Менял только основные моменты.

Давайте разберем.

Значит, смотрите, я поменял background, сделал его вот таким темным.

Дальше foreground — это цвет текста, я его оставил таким.

По-моему, это белый текст.

Вот видите, в чем проблема HSL-формата?

HSL — это переводится, по-моему, первую букву не помню, потом situation и lightness, по-моему, так это переводится.

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

Мы с вами еще поиграем с этим HSL, поэтому все разберемся, не переживайте по этому поводу.

Дальше muted, ну, собственно, цвет для текста muted формата, popover, это всплывающая история карт, border и input, sidebar, цвет sidebar отдельно я указываю, да, и он также для карточек будет применяться, primary цвет основной, secondary цвет, акцентный цвет, ring, собственно, ring тоже иногда используется для обводки, и radius я ставлю по умолчанию 0.53.

сохраняем, в принципе, да, можно закрыть, да, все сделали, да, ну, единственный момент, помните, я вам говорил, что если мы используем здесь hex-формат, а не HSL, да, то мы тогда переходим в nuxt-конфиг, ой, точнее, в tailwind-конфиг, и здесь меняем, просто ручкой меняем то, что нам нужно, чтобы я сейчас все не менял, опять же, я это возьму готовенько, вставлю просто,

Так, вот так возьмем, чтобы я просто сейчас не вспоминал, что я там конкретно менял, а что не менял.

Так, берем наш замечательный Colors, вот сюда идем и вплоть до карты мы полностью меняем, вот таким образом.

Так, по-моему, скобочка лишняя, мне кажется, вот так отлично будет.

То есть здесь видишь, как оно вперебой идет, там где-то HSL используется, где-то нет.

Короче, если будете делать свой проект, просто берите везде HSL, поставьте везде адекватные вары и не парьтесь.

Так будет максимально комфортно.

Опускаемся ниже, тут мы ничего не трогаем, это дефолтная история по умолчанию.

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

Timing function у меня будет easy in out Это эффект анимации непосредственно Вот я по умолчанию везде такое ставлю Мне нравится, как это смотрится Сохраним Так, обновимся, что у нас в итоге получается Sidebar пока что пустой Давайте его как раз таки сейчас стилизуем в самое время Напишем здесь bg sidebar А уже, кстати, написано

Но цвет я не обнаруживаю почему-то, что очень странно.

Давай посмотрим BG Sidebar.

Опустимся ниже, где наши Tailwind'овские цвета.

Опускаемся ниже.

А мы их, кстати, и не видим, насколько я понимаю.

Мы их, по-моему... А, нет, мы их видим.

Вот он.

Border Sidebar.

Вот наш цвет Sidebar.

Но он успешно игнорируется.

Ну, боюсь, что надо перезагрузить приложение, потому что что-то не так.

Что-то явно не так.

Возможно, стоит нам выполнить команду очистки кэша.

Давайте я сразу вам покажу.

Вот такая команда npx naxe client-app.

И я через амперсанты, через два амперсанта ставлю yarn-dev, чтобы сразу после очистки запустился проект.

Вот.

Ну, чтобы в одной команде две сразу выполнить.

Все, он очищается и запускается.

Давайте посмотрим, что сейчас получится у нас, какая картинка.

Да, все заработало.

Как видите, нам нужно просто очистить кэш, тогда все заработало прекрасно.

Так.

С этим понятно, кнопочка вроде бы работает, но я в ней никакого текста не вижу, потому что у нас, скорее всего, foreground что-то не назначается.

Давай посмотрим еще раз, чуть детальнее.

А, у нас цвет идет, смотри, цвет в этот идет почему-то.

А я знаю почему, потому что давай перейдем обратно в Tailwind CSS, и вот у нас есть background, есть foreground.

Вот, есть Primary, есть Primary Foreground, вот, и мы забыли, да, что Primary у нас, окей, фиолетовый, но Primary Foreground должен быть белый, потому что, ну, это то, что будет сверху на фиолетовом цвете отображаться, поэтому я вот такое, знаешь, чуть-чуть серовастенький возьму, сохраню, вот, все, теперь отобразился белый цвет.

Это в случае, если кнопка будет использоваться именно в таком формате.

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

Вот, default variants.

Будет у нас не default, а непосредственно, по-моему, мы будем secondary использовать чаще.

Давай посмотрим secondary вариант.

Вот опять же, видишь, закэшировалось походу.

Боюсь, что закэшировалось.

Давай ему подредактируем немножечко.

Да, видишь, я вам говорю, очень важно во view кэширование.

Вот оно реально вам может помешать, поэтому выполняйте эту команду, когда что-то у вас не обновляется.

Вот я обновил, обновил, теперь все заработало.

Все, отлично.

Дальше, еще один хочу моментик небольшой сделать.

Так, у нас сейчас стоит, значит, размер сайта 125%, я сделаю ресет, чтобы он был мелким сайтом, да, и я, короче, объясню концепцию, да.

То, что вы видели демку проекта, я ее разрабатывал достаточно в крупном формате.

И я бы мог сейчас продолжить делать именно так же.

Но я потом подумал, что вы этот проект потом себе будете скачивать, и он у вас будет криво отображаться.

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

Поэтому мы его чуть-чуть изменим в сравнении с демкой нашей.

И нам нужно просто в Tailwind немножечко поменять определенные значения.

То есть нам просто нужно сайз сделать крупнее Просто крупнее Да, сначала попробуем такой лайфхак небольшой Я перейду сюда И попробую здесь указать фон сайз Фон сайз 20 пикселей Сохраним

Ну, собственно, мы ничего не увидим.

Что логично, оно так не работает.

Я просто думал, что он может быть отследуется от body размера, но нет.

Но нет, не так все просто.

Поэтому мне нужна буквально минутка-минутка, и сейчас я все подредактирую.

Итак, значит, что нужно сделать, ребят?

Идем в tailwind-config.

И все базовые цвета поднимем просто на несколько пикселей, на несколько поинтов.

То есть у нас есть Extend.

Это когда мы расширяем.

Что значит расширяем?

Мы не перезаписываем старые значения, мы просто на основе них расширяем.

А нам нужно не Extend,

Или нам Extend позволит это сделать, я не знаю.

То есть, смотрите, если мы, как здесь указываем, контейнер просто, то он перезапишет.

Наверное, нам лучше перезаписать.

Вот, я перезаписываю все фонсайзы.

Вот, XS такое, SM такое, Base такое и так далее.

И вот эти штуки я уже добавил от себя.

Вы можете их убрать.

Опять же, в демке их не было, и мы их, скорее всего, использовать не будем.

Но для себя, в своих проектах я это использую.

Поэтому вам будет так на заметку, так бонус, скажем так, приятный.

Посмотрим, что получается теперь у нас.

Обновляем.

Опять же, мелковато все.

Не обновился кэш, к сожалению.

Придется почистить опять.

Жалко, что оно так, конечно, все кэшируется жестко прям.

И не дает нам нормально.

Может, какие-то есть другие кейсы обновления.

Вот, уже стало крупнее.

Я вижу это вот, поэтому стало реально крупнее.

Но нам нужно еще крупнее.

Нам нужно значительно крупнее.

Поэтому я предлагаю каждый элемент увеличить по размеру.

Давайте, чтобы как-то сориентироваться, я, наверное, увеличу размер сейчас SM.

Давайте посмотрим «Button».

Я хочу это сделать, чтобы просто у нас красивый сайт был.

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

Значит, перейдем сюда и посмотрим, что у нас у Бата найдет по умолчанию, какой размер текста.

У него идет текст SM.

Текст SM — это, собственно, маленький размер, поэтому я сразу укажу Base.

По-моему, да, Base...

Подожди, ну бейс должен работать.

Странно, почему здесь не подсказывают, но здесь он не должен подсказывать.

Так, бейс.

Смотрим сюда.

Текст, давай опустимся, опустимся.

Текст SM все равно не обновляется.

Блин, это что-то тоже странная история, должно происходить обновление.

Так, смотрим сюда.

Да, TextBase поменялся.

Вот он у нас замечательный.

1.18.

В принципе, меня он устраивает, наверное.

Я бы поставил ему, наверное, 1.

Я, наверное, ему 1.4 бы поставил.

То есть это примерно на 15%, наверное.

Даже на 20%, да?

Да.

Я, короче, все шрифты увеличу на 20%.

Давайте сейчас за кадром я это все сделаю, чтобы вам ваше время не тратить.

Так сказать, ChargeGPT в этом случае очень сильно помогает.

Вот, ставили.

Правда, комментарий написал, ну ладно, фиг с ним, уже так оставим.

Так, обновимся.

Ну, вроде бы все заработало, но опять же, я думаю, потребуется обновление кэша, я не знаю.

Короче, у меня какой-то трабл, видимо, с проектом.

Что-то не ладится.

Так, все, теперь оно все точно работает.

Окей, дальше пойдем.

Дальше пойдем.

Я на 20% сделал больше, я думаю, достаточно будет вполне.

Так, с этим мы порешали все.

Значит, сайтбар с этим понятно.

Так, чем мы займемся дальше?

У нас все готово.

Шрифт подгрузили, это подгрузили.

Это мы тоже подготовили с вами.

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

Помните, она даже с анимацией была.

Кто заметил, тот заметил.

Поэтому сделаем здесь сразу же, наверное, button.

Button.

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

Сейчас, кстати, иконочный шрифт надо будет поставить.

И накинем определенные стили.

Вот такие стили.

Absolute, Top 2, Ride 3, Transition Colors.

И при наведении будет фиолетовый оттенок.

Так, ну даже у нас не фиолетовый, можно и Primary указать.

На самом-то деле у нас теперь Primary фиолетовым является, поэтому можно и Primary указать.

Должен работать.

Так, давай теперь укажем иконку.

Так, с иконкой, ребят, icon.

С иконочкой нам нужно поставить с вами next icon непосредственно, поэтому давайте этим и займемся.

Пока что всем, чем мы занимаемся, ставим нужные библиотечки, ребят.

Ну, потом займемся уже разработкой, когда все уже будет стоять.

Короче, есть next icon.

Значит, что я могу про нее сказать?

Это вот штука, которую предоставляет сам next, которая в себе содержит очень много иконок.

Они все берутся с сайта Iconify.

То есть вы берете, переходите на Iconify, оттуда можете все брать без проблем.

Также от Iconify есть экстеншн для VS Code, который позволяет вам подсвечивать нужные вам шрифты.

Но честно вам сказать, что это все работает ужасно.

То есть по сравнению с тем же Lucid Icons в React это все сильно слабо.

Поэтому я бы порекомендовал использовать другую

какую-то библиотеку для шрифтов, ну, для иконок на ваш вкус и цвет, опять же, я не знаю, что лучше взять, опять же, есть ли какое-то идеальное решение или нет, я его не нашел, честно признаюсь, поэтому решил использовать по умолчанию то, что предоставляет сам Nux, то есть официальную библиотеку.

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

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

И я вот эти вот иконки сайдбара еле-еле выискивал, потому что сложно найти подходящие иконки с разных стоков, скажем так, да.

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

вот, ну, короче, вот такая тема, поэтому я думаю, что то, что мы используем официальную библиотеку, это, наверное, самое такое оптимальное решение, которое подойдет всем, потому что здесь особо выбора у нас и не было, перейдем в next-config, значит, дальше мы, дальше, дальше мы вставим, так, давай прямо вниз сюда укажем, next-icon, сохраним,

Закроем.

И можно нашу первую иконку инициализировать.

Как она работает в целом, да?

Во-первых, вы ставите себе вот этот IntelliSense.

Видите, здесь ссылочка есть.

Спокойно ставите.

Ну, можете перейти.

Вот так она называется.

В поиске будете, ставите.

И она вам будет подсвечивать ваши иконки.

Вы пишете название библиотеки иконок, айконсета, да?

И через двоеточие указываете непосредственно вот GitHub, да, к примеру.

Тут, кстати, рекомендую еще какой-то посмотреть такой сайт.

Я не знаю, на нем не был, честно вам признаюсь.

Ну, короче, видимо, тоже что-то, какой-то прикольный сайт.

Может быть, я его пропустил как-то.

Вот, я, короче, брал из сайта с иконкой Iconify.

Вот, выпишешь название IconSet и через двоеточие указывается саму иконку.

Давайте попробуем эту штуку реализовать.

Значит, нам нужна иконка выхода из системы.

И напомню, она у нас анимированная.

Для этого мы пишем, собственно...

Указываем просто icon, указываем просто name.

И, как видите, с помощью extension иконка сама у нас подсвечивается.

Вот, видишь, она анимирована такая красивенькая.

Ну, вот что вам нужно написать.

line.md, это, по-моему, с материала дизайна, двоеточие, logout.

Тут, чтобы нам иконки воспользоваться вот в нашем наxt, просто пишем icon и указываем название иконки.

Все, больше нам ничего здесь не требуется.

Идем сюда, обновляем.

Как видим, иконка появилась.

Вот она красивенько анимирована.

Сейчас обновляем.

Вот она красиво рисуется.

Мне кажется, конфетка.

Так, по размерчику.

Я чуть покрупнее хочу сделать явно.

Она слабенькая по размерчику.

Давайте я поставлю ей 30 пикселей.

Ну, 30 многовато.

Где-то 22, наверное, будет вполне нормально.

Так, у нас что-то по макетам.

Давайте примерно прикинем по соотношению.

Наверное, 22 окей.

Даже может быть 20.

Лучше будет поставить.

Вот так будет нормально.

И при наведении она, видишь, становится фиолетовым.

Идеально, то, что нам нужно.

По макету пока все совпадает.

Дальше нам предстоит сделать менюшку непосредственно.

Менюшка у нас должна быть как бы отдельным таким массивом, да, и по нему будем мапиться, то есть по нему будем пробегаться с помощью цикла, или вовью это не map, а for, да, с помощью v4 утилиты мы будем все это распаковывать и с помощью цикла выводить все наши элементы.

Но для начала давайте создадим наш каркас непосредственно под менюшку.

Для начала создадим саму менюшку.

Пишем здесь новый файлик, пишем menu.datum.ts.

Сначала создам интерфейс для удобства export-interface-i-menu-item.

Это вот каждый элемент меню, какой у него будет интерфейс непосредственно.

Здесь все очень просто.

У него будет имя по типу, как строка, и все остальные тоже будут строки.

URL тоже строка.

И icon тоже строка у нас будет непосредственно.

Вот, к сожалению, опять же повторюсь, в том же самом React, допустим, с помощью нормальных библиотек иконочных мы можем юзать вместо icon, то есть вместо string можно прям прокидывать icon type определенно.

Здесь такой нет вариации.

Опять же, возможно, я эту библиотеку иконок взял, но в дефолтной библиотеке иконок такого нет.

Это важно понимать.

И нет никакой типизации.

То есть нет, знаешь, какой-то такой базы, где нам...

давали бы подсказки, какие у нас иконки вообще в целом есть.

Наверное, это невозможно, потому что их слишком много.

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

Это реально удобно.

И сделаем саму нашу меню дата.

Export const меню.

Меню дата я напишу.

По типу будет как imenu, то, что мы указали выше, только массив.

Все, теперь здесь можно описывать наши иконки.

Давайте опишем первый пункт.

У нас будет это home, то есть icon у нас будет radix icons

Через двоеточие он вам будет подсказывать, когда вы напишите название библиотеки, название айконсета.

И у нас это будет дашборд.

Дальше запятая у нас будет name, это home.

И адрес, по которому будет открываться home URL, это просто вот такой слэш.

Вот, и таким образом описывайте каждый вот пункт, который вам нужен.

Либо там в вашем приложении можете сделать какие-то другие пункты меню.

Это, опять же, на вас вкуса цвет.

Я свои пунктики скопирую, чтобы не тратить ваше время по понятным причинам.

Значит, что мы тут сделали?

Давайте покажу.

Products у нас будет EP Goods.

Payments будет PH Contactless Payment.

Orders будет Fluent Receipt 28 Regular.

Customers у нас будет MingCat Group Line.

Фидбэк.

от Fluent, потом Setting от Radix, и вот такой Question Mark тоже от Radix.

Сохраняем, и вот теперь у нас меню Data готово.

Теперь нужно нам по этой меню Data пробежаться, и на каждой итерации вывести определенный пункт меню вот этого.

Давайте я приближу, чтобы вам было лучше видно.

Вот каждый пункт меню нужно будет вывести.

Создадим компонент меню для этого, меню .view.

И все, что мы вначале сделаем, это возьмем наш скрипт замечательный.

Жалко, что нет адекватного сниппета, который его распаковывают.

Давайте попробуем поставить другие сниппеты.

Вот чисто для разнообразия.

Может быть, реально они лучше будут.

Ну-ка, давай попробуем скрипт.

Так.

Так, пока что мне не нравится.

Давай эти отключим.

Подожди.

Давайте отключим.

Disable.

И эти оставим.

Так.

И я перезагружу все экстеншены.

Так.

Пишем скрипт.

А это вообще не работает.

Такое чувство, что он вообще не работает.

Здесь что-то надо настраивать, походу.

Или он как-то не так... Подожди.

Он, по-моему, не так работает.

Смотри.

Здесь скрипт.

V4.

Vbase.

А, здесь через Vbase.

Смотри.

Vbase TS.

Вебейс 3 ТС, 3С сетап.

Слушай, ну не суперкомфортно, знаешь, ну как бы он неплохой, но, блин, сложно.

Сниппеты должны быть короткими.

Ну вот, вот эту штуку надо брать.

Но опять же расположение, да, то есть вообще скрипт он вверху находится в новом вью.

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

Ладно, включу все-таки эти, которые я вам в начале ролика показывал, мне их будет достаточно вполне.

Для чего я взял здесь скрипт-сетап?

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

Так, стили нам здесь не потребуются, у нас будут элвиновские стили.

И дальше у нас будет div и next-link непосредственно, вот наш next-link Значит, по классу будет вот такая тема, то есть это у нас flex, item-center, паддинги, округление небольшое, чтобы при наведении было красивое округление Что будет при наведении происходить, потом еще тень добавляем при наведении, transition добавляем и margin-bottom

Вот, и тут мы будем отображать нашу иконку непосредственно и наш спан, да.

Как у нас во Vue вообще элементы вывести?

Смотрите, что вывести элемент, давайте сейчас это закомментирую пока, давайте попроще начнем, чтобы вам было все понятно и разжеванно.

Значит, смотри, здесь сделан v-form, v-form.

Здесь мы указываем item, на каждой итерации будет каждый элемент называться item.

In, откуда будет браться?

Из меню, меню дата, от которой мы краски импортируем отсюда.

Но опять же, видишь, он type импортирует почему-то, хотя это не является type, это является просто константой.

Он почему-то по ошибке это делает, не знаю почему.

Здесь ключ, не забываем, как в React тоже нужен ключ, item.name будет являться ключом.

Или можно URL, как уже на ваш вкус и цвет.

И дальше, куда будет идти сама ссылка?

To, потому что мы используем next link, помним, да?

И item URL.

Прямо сейчас я вам показал, как мы интегрируем перемены в нашем компоненте Vue.js.

То есть, что я имею в виду?

Смотри.

У нас есть переменная item, к примеру.

Неважно, где она будет располагаться.

Либо мы ее здесь создадим, допустим, const item 0.

Также мы ее можем спокойно отсюда использовать.

Без экспорта просто вот так мы ее отобразили.

Теперь мы можем спокойно ее отсюда использовать.

Либо мы можем вот здесь с помощью цикла ее сделать, к примеру.

Опять же, те, кто там любит React, не обращайте внимания.

Здесь очень много магии.

Для вас это может показаться странным максимально.

Просто успокойтесь и смотрите, что я делаю.

Это реально может показаться на первый взгляд немножко странноватым, но ничего страшного, потерпите, скажем так, да?

В этом есть свой шарм определенный.

То есть вот это так все происходит, да?

И теперь, чтобы нам переменную указать, допустим, в атрибуте в каком-то, нам этот атрибут нужно перевести в динамичный момент.

Я не знаю, как это по сленгу называется, по терминологии.

Ну, короче, мы указываем двоеточие перед атрибутом, тем самым он нам позволяет в него теперь писать полноценный переменный, полноценный JavaScript-код.

Допустим, вот item, icon.

Вот.

Вот такая тема.

И, то есть, если мы просто пишем без двоеточия, он нам не позволит это сделать.

Ну, он почему-то сейчас не ругается, потому что это, ну, короче, он как строку воспринимает.

Но чтобы это была не как строка, а как переменная, ставим двоеточие «to».

Стойте, стойте, ребят, а что это такое?

Именно вы, ребят, да?

Вы меня просили снять в прошлом ролике ролик про авторизацию.

Все пишут комментарии, Макс, где ролик по авторизации, когда он выйдет?

Уже вроде бы набрали лайк, когда он выйдет?

На самом деле, ребят, сейчас я вам скажу, когда выйдет ролик по авторизации, смотрите.

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

Я хочу провести первый в своей жизни мастер-класс.

Мы с командой его подготовим невероятно круто, качественно.

Я весь код заранее подготовлю.

Пройдет он, ребят, внимание, 16 декабря в 10 часов утра, да, чтобы у вас была возможность у каждого зайти в онлайне условно, да, или у вас также будет возможность посмотреть просто в записи.

Запись останется навсегда, так что не парьтесь по этому поводу.

Вход будет стоить 1490 рублей.

Что вы получаете за эти небольшие деньги, ребята, это прям вообще копейки.

Если бы у меня была такая возможность, я бы купил, ребят, честно признаюсь.

Короче, это мастер-класс, где мы разберем три вида авторизации.

Я полностью на примере кода, на примере реального проекта покажу вам.

Вот самая примитивная авторизация, где мы там сделаем с использованием готовых там бэкэндов условно.

На втором уровне мы возьмем авторизацию, уже сами ее напишем, но такую тоже простенькую.

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

И вот такой у нас будет полноценный мастер-класс, где я разберу три стадии авторизации.

От простой до легкой.

То есть этот мастер-класс...

Подойдет каждому.

Он будет идти, я думаю, там 3-4-5 часов.

В среднем, да, в такой формате стрима будет.

Без отвлечений, без воды.

Сразу обещаю.

То есть вы условно платите 1400 рублей и получаете, ребят, полноценный гайд, как сделать авторизацию.

Все исходники для вас будут также бесплатно доступны.

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

И это прям полноценный мастер-класс, посвященный авторизации.

Что может быть еще лучше?

Я думал, какую тему можно осветить, которая нужна каждому разработчику.

А каждому разработчику нужна авторизация.

Многие из вас не умеют ее делать и не понимают, как она устроена.

Как там вот эти токены обновлять?

Как сделать так, чтобы у меня при неудачном запросе токен обновился и вернулся обратно?

Как работать с интерсепторами в Axios?

Или как, допустим, для новичков сделать вообще в целом хотя бы базовую авторизацию?

Вот эти все три стадии от простой к сложной авторизации мы с вами разберем.

То есть три проекта будет.

Напоминаю, что это реально мастер-класс, только по авторизации.

Ничего больше не будет.

Мне кажется, такой выжимки нигде нет.

Если бы я в свое время мог заплатить полторы тысячи рублей...

и узнать, как делать авторизацию, я бы отдал все просто.

И причем разбирать мы будем на примере нового Next.js 14.

По ссылке в описании записывайтесь, оплачивайте, залетайте, собственно, и встретимся с вами уже вместе 16 декабря в 10 утра.

Запись также будет доступна, так что кайфуйте, ребят.

Вот, что же нам делать, если нам нужно вывести переменную именно вот как здесь мы выводим ее в спане?

В обычном JSX в React нам понадобятся бы обычные фигурные скобки, одинарные, да, и все это дело вывести.

Здесь нужны двойные фигурные скобки, как в том же, по-моему, в Angular также используется, и в Svelte, по-моему, также используется, и вводим сюда переменную непосредственно.

Вот таким образом.

Теперь мы вывели сюда имя.

Давайте пробуем это отобразить теперь.

Как мы это отобразим?

Идем в Sidebar.

Вот наш замечательный Next Link Button.

И теперь, опять же, без лишних импортов, мы просто берем Sidebar в Layout, значит, пишем Layout Sidebar.

И сохраняем.

Посмотрим, что у нас получилось.

Пока что пусто.

Смотрим консоль.

В чем проблема?

Проблема у нас... Что-то непонятная какая-то проблема.

Давайте обновимся.

Так, смотрим сюда в консоль.

Что-то мы забыли сделать.

Так, Maximum Call Stack.

Что-то у нас не получилось.

Что-то мы упустили с вами.

Так, Layout Sidebar мы взяли.

Так, значит, Template.

Подожди, layout, sidebar, layout, sidebar, сюда идем.

А, я, ребят, заговорился, мы берем, конечно же, у нас вызовется максимум cold stack, потому что мы перевызываем внутри sidebar, еще один sidebar вызываем, так нельзя делать, поэтому здесь layout не sidebar, а меню.

Я просто заговорился с вами, поэтому пишем layout, меню, сохраняем.

Обновляем.

Как видите, вот наша менюшка появилась.

Красивенькая, нарядненькая менюшечка.

Она мельковастенькая немножечко.

Давайте мы ей увеличим размеры.

Значит, перейдем в Layout Menu.

Значит, смотри, здесь, во-первых, у нас сами айтемы.

Так, давай посмотрим, размер есть, размера нет.

Давайте, во-первых, смотри.

Во-первых, я перейду в Tailwind.

И я укажу, что по умолчанию у нас будет TextBase.

Вот так, text-base, чтобы у нас сразу был по умолчанию крупный размер.

Вот так я сделаю, чтобы они сразу были крупненькими.

Да и в принципе на меня это достаточно будет.

Я что-то думал поменять, но, наверное, достаточно мне этого вполне.

Единственный моментик, наверное, мне кажется, капельку перебор получился по размеру.

Чуть-чуть перебор.

Я бы в tailwind-config сюда перешел и чуть поменял бы.

То есть я бы сделал все-таки 38 где-то, здесь 46.

Так, я основные поменяю.

Дальше уже там идти не буду.

Здесь сделаю 1.2, здесь сделаю 1.05.

Сохраним.

Вот что-нибудь такое сделаю, чтобы оно хоть немножко поменялось.

Но чтобы поменялось, нужно опять очистить кэш.

Так, давайте я порт уберу.

Я порт просто тестировал.

Давайте вот так сделаем.

Просто на ксиклианап и чистим.

Я просто уменьшу размер шрифтов в целом, чтобы было поменьше.

Так, давай посмотрим, обновимся.

Ну, вот так уже стало гораздо лучше.

Вот, и между ними, наверное, отступ тоже чуть-чуть прибавить.

Слишком они приплюснуты получились.

Идем в меню.

Вместо Margin Bottom 2 сделаем Margin Bottom 2.5, к примеру.

Вот так мне нравится больше.

Все, видишь, эффект работает, все работает.

У меня почему-то курсор Pointer здесь не отображается, не знаю почему.

При этом он, скорее всего, есть.

Давай примитивно его зададим, принудительно, точнее, Pointer зададим сюда.

Не, он просто мой браузер подлогнул, то есть это не проблема в поинтере.

Видишь, у меня поинтер не работает.

Ну, окей.

Не так важно, опять же.

Давайте мы это уберем.

Так, сохранили.

Окей.

Как будто у нас теперь логотип маловастенек на фоне этой всей картины.

Давайте логотип чуть побольше сделаем, по 120.

Так, что у нас сейчас получается?

120.

Ага, это он уже крупнее стал, да?

Что-то я даже не заметил, как будто.

Вот, вот так, наверное, будет лучше.

Так, смотрим.

Вроде симпатичненько смотрится.

Правую часть, видишь, мы пока что оставили.

Ну, как вам, ребят?

Мне кажется, симпатично смотрится, достаточно неплохо.

Так, с этим мы тоже порешали с вами.

Теперь, ребята, предлагаю вам приступить к авторизациям.

Мы сначала сделаем авторизацию с вами, чтобы у нас пользователь мог войти в систему, и на основе этого мы будем уже делать дальнейшие с вами компоненты.

Потому что нам нужно сделать и загрузчик, чтобы при логине приложение стопилось, да, экран загрузки был, и чтобы все грузилось.

Ну, короче, полноценная SPA, Single Page Application.

Вот, для этого нам нужно сделать авторизацию.

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

Вот, поэтому мы, наверное, с вами займемся тем, что реализуем авторизацию непосредственно.

Авторизацию, да, но перед ней реализуем app-write, app-write-config, вот, потому что она нужна для авторизации.

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

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

Вот, то, что получилось.

Итак, ребята, нам предстоит теперь сделать app.write.config плюс авторизацию.

Вот такие два важные ключевые момента.

Окей, здесь что нам важно понять?

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

Для этого нам нужно создать сам загрузчик, который будет на весь экран.

Помните, не так давно мы с вами добавили loader, вот такой означает loader.

Он прекрасно себя показывает, но давай теперь создадим сам компонент под этот loader.

Идем в layout и создадим сам loader.

Компонент будет максимально простым, вот в layout и сам loader, потому что он будет относиться именно к layout.

loader.view, вставляем template div class wrapper, который здесь мы описываем, image вот такой-то.

Здесь что, ну, можно alt дописать, да, для, ну, типа, для правильности, скажем так.

bg sidebar, это его фон общий, который будет, то есть, цвета сайдбара, item center, justify center, то есть, нам важно выровнять по центру наш loader.

И ширина, то есть, максимально по ширине экрана будет располагаться он.

Вот, мы им воспользуемся чуть попозже, когда займемся уже непосредственно авторизацией.

То есть, когда именно не авторизацией займемся, а когда мы займемся уже реализацией проверки, то есть самой нашей авторизацией.

Давайте для начала мы перейдем в app.write, зарегистрируемся там и, собственно, создадим первую нашу базу данных.

Напомню, AppWrite, ребята, это штука, которая нам позволит не писать бэкэнд и сэкономить на этом время.

То есть, чтобы мы вместо бэкэнда просто собрали бэкэнд на базе AppWrite и не парились.

Потому что так проект получится очень большой.

И я думаю, по времени этот ролик будет достаточно много идти.

Посмотрим, сколько получится после монтажа.

Но тем не менее.

Для этого давайте зарегистрируемся.

Переходите в AppWrite, регистрируйтесь.

У меня уже аккаунт есть непосредственно.

Я регаться повторно не буду.

Но тем не менее, создадим с вами теперь... Вот вы зарегались.

Новую базу данных.

Create Database.

Нажимаем сюда.

Создаем базу данных.

Она у нас будет называться... Давайте мы ее назовем, пожалуй... Так.

Или давай даже проект.

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

Давайте проект создадим новый.

Ну, вы именно так будете начинать.

Он будет называться CRM System.

Значит, next...

Рекорд.

Ну, такое название, да?

Project ID, опять же, можно менять.

Вот.

Можно под себя сделать.

Я рекомендую, наверное, под себя сделать, чтобы было комфортно, особенно для коллекции.

Мы это дальше разберем.

Я напишу краски то же самое, только в качестве ID.

Мне кажется, так будет максимально круто.

Так, create.

Все, create нашу базу данных.

Точнее, сначала проект.

А в нем уже будет и авторизация баз данных и так далее.

Так, создание прошло успешно.

Теперь мы с вами идем в, ну, можно инструкцию открыть сразу же, без проблем.

Это уже можно сделать, можно нажать веб, к примеру, да, написать наше приложение.

У нас будет приложение, собственно, так же напишу, hostname будет localhost, localhost.

Потом, когда будет деплоить, напишите свой там домен условный.

Next.

Вот что нам требуется установить.

Давайте установим app-write.

Значит, пишем yarn etc.

app-write.

Устанавливаем сразу же.

Потом мы идем сюда и, значит, копируем эту историю.

Ну, мы ее немножко по-другому сейчас реализуем.

Опять же, делаем дальше.

Вот такую штуку делаем.

Для начала давайте зададим, собственно... Так, у нас будет... Смотрите, у нас есть утилиты уже, а есть библиотечки.

Я лучше сделаю в папке lib...

Наверное, так будет даже получше, мне так кажется, почему-то.

Или давайте в утилсах сделаем, окей.

Напишем app.write.ts.

Здесь я вставлю свой код и объясню, что мы делаем.

Смотрите, во-первых, нам потребуются константы.

Да, мы их сейчас создадим.

Это там, где будут храниться все ID-шники нашей базы данных.

То есть это ID базы данных, это ID коллекции, ID сториджа для загрузки картинок и так далее.

Все это будет храниться там.

Поэтому сейчас создадим этот файлик обязательно.

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

Ну, это для авторизации, там, получить текущего юзера, там, получить его профиль и так далее.

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

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

Вот, здесь инсталлизируем наш клиент, new client, дальше назначаем ему endpoint, set endpoint Это у вас у всех будет дефолтный endpoint, а дальше указываем ID нашего проекта, который мы только что написали Вот он, как раз-таки вот здесь, давайте его скопируем, вот такой он ID-шник у нас идет Но опять же, я не рекомендую делать вот таким образом, я рекомендую вынести в отдельную константу А еще лучше, если будете делать на продакшен, вынесите в файлик env

Это вам так на будущее, немножко на будущую перспективу, скажем так Вот, я сам файлик сделаю прямо здесь app-constants app-constants.ts Окей, constants Вот, и здесь будем, собственно, все это дело вставлять Давайте я вставлю эту штуку Я заранее подготовил уже готовые константы, которые мы сейчас наполним просто Значит, нам нужно вставить app-write-id, вот сюда вставляем Также db-id сейчас мы сделаем и для коллекции, и для сториджа

Вот, не забыть Хотя давай все-таки оставим на место, вот так и оставлю Вот, здесь мы можем импортировать app-constants Вот, он пока что его не видит, хотя на самом деле должен видеть Но я думаю, что давайте мы сейчас сделаем с вами restart-extension-host Должно он его увидеть, по крайней мере Так, ну как видишь, да, все успешно заработало Так, отлично, app-вратность работает Дальше

И дальше, дальше, дальше.

Вот так мы базово инициализировали, обязательно экспортируем ID-шник, он нам может потребоваться.

На самом деле, по правде говоря, я его так, по-моему, и не использовал ни разу, потому что не пригождалось, знаете, у меня почему-то ID, вот полезная штука в ID-шнике, да, это ID-уник.

Но он у меня так и не работал, этот ID Unique, поэтому не знаю.

Короче, я его так, по-моему, и не использовал.

Вот можете, в принципе, его даже отсюда убрать на самом деле.

Ну, я оставлю по умолчанию, пусть висит.

Дальше делаем Next и Go to Dashboard.

Все.

Вот наш замечательный проект.

Можно перейти в авторизацию.

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

Я вот думаю, может быть, прямо сейчас нам сделать все остальное.

Давайте некоторые настройки сделаем, перейдем в Settings.

Хотя перед сеттингом, наверное, нам бы не помешало сначала создать коллекции, чтобы к ним доступ выдать.

Короче, давайте создадим сначала коллекции.

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

Касаемо баз данных, смотрите, что это такое, ну точнее, что такое коллекция, коллекция это, проще говоря, таблицы в базе данных, вам не обязательно знать бэкэнд, вам нужно знать просто основы, что это такое, то есть у нас у каждой сущности есть своя таблица в базе данных и между ними есть какие-то определенные связи, потому что, допустим, в нашем случае у нас есть deals, то есть это наши сделки и у каждой сделки есть связь и с комментариями, с comments, в каждой сделке есть свои комментарии и связь с кастомерами, то есть в каждой сделке прикреплена определенная компания, к которой мы обращаемся.

Это все вот так между собой перелинковано.

Давайте этим займемся.

Нажмем Create Database, создадим первую базу данных.

Назовем ее непосредственно Deals.

Это наши сделки.

А тут напишу опять же так же Deals и Create.

Тут можно сразу же все поменять и подредактировать.

Deals, сразу же Customers.

И comments.

Я сразу так напишу, потому что я знаю, как это все у нас будет выглядеть.

Так, что получается у нас?

Что-то не сработало или сработало?

А, сработало, просто оно хотело, чтобы мы создали непосредственно коллекцию.

Вот, коллекция создалась успешно.

Теперь мы создаем...

Или я неправильно был?

Коллекция — это немножко другое, да?

Подожди, коллекция.

Подожди, стой.

А, ребят, извиняюсь, я перепутал немножечко, заговорился с вами.

Это мы database делаем именно.

Это, подожди, это мы удаляем.

Неправильно сделал.

Вот удаляем.

Это мы сейчас database создаем, а вне уже коллекции делаем.

Это я немножко поторопился.

Create database.

Вот, database будет называться у нас, ну, условно, CRM base.

Особо думать-то я не буду.

Вот так создадим просто и сразу здесь добавим.

Это CRM base, наша database будет.

Создали сразу же, да?

И create collection.

Вот.

А теперь мы создадим коллекцию, то есть deals, к примеру.

Тут тоже делаем deals.

Значит, create.

Вот, deals создали.

Теперь он нам просит создать атрибуты.

Атрибуты — это поля в нашей таблице в базе данных.

То есть, условно, у нас может быть строковый, числовой, float, boolean и так далее.

Давайте создадим строковый.

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

Так, удился будет «Name», «Price», «Customers», «Status», «Comments».

Давайте «Name», «Price».

Так, «Name».

Указываем размер.

Я рекомендую указывать чуть побольше, потому что, ну, разные будут названия.

Давайте 44 будем указывать, я думаю.

«Default» на «Name» у нас не будет.

То есть «Name» — это обязательное поле.

Ставим «Required» — это обязательное.

И «Create».

По такому же принципу создаем поле email.

Это наши, подожди, не email, а price, прошу прощения, price, да, create attribute, integer, price.

Минимально, ну, можно это не указывать, в принципе, я думаю, что так оставим.

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

Можно указать, если вам так хочется, скажем так.

Ну, допустим, у нас минимальный price может быть 0, а максимальный price будет, ну, типа там условно не больше там каких-нибудь там,

10 миллионов, наверное.

100 тысяч.

Раз, два, три.

Давай вот такую цифру укажем.

Больше точно.

Давай еще нолик добавим.

Больше точно не будет.

Дефолтного не будет.

Required поле обязательное.

Идем дальше.

Дальше нам еще нужно, по-моему, создать поле статус.

Оно будет enum типа.

Это важно.

И все остальное это уже идут связи.

Давайте enum выберем.

Что такое enum тип?

Те, кто стоит с криптом, не знаком.

Enum это когда мы фиксируем какие-то определенные значения полей.

То есть у нас это вроде бы является строкой, но при этом мы четко фиксируем значения.

Это актуально, к примеру, для ролей, когда мы знаем точно, что у нас есть определенные роли.

То есть, допустим, роль админ, роль студент, роль там эдитор, к примеру, да.

Или когда нам нужно в нашем случае зафиксировать значение статуса.

Статуса могут быть.

У нас 5 штук статусов.

Вот видишь, 5 штук статусов.

Это входящие на согласование в производстве, произведено к отгрузке.

Это все наши фиксированные статусы.

Я решил их сделать именно фиксированными.

Поэтому вот это называется Enum.

Здесь делаем статус.

Тут описываем те самые элементы.

Давайте откроем здесь.

У нас это «to do», «to be agreed».

Давайте писать будем to do.

Так, немножко тут to do и пробельчик.

Так, to do, пробел.

To be agreed.

Дальше у нас идет in progress.

In progress.

Дальше у нас идет produced и done.

Done это завершено.

Produced и done.

Вот такие будут у нас штучки.

Дефолтное значение to do, то есть сначала попадает в первую колонку в любом случае.

Это поле у нас обязательно.

Да, ну как бы можно не ставить обязательно, потому что to do по умолчанию идет.

Вот.

И create.

Это чисто формально, на самом деле, можно было вообще и ставить по умолчанию, без разницы, на самом деле.

Вот, связь пока сделать мы не можем, потому что у нас не с чем связывать, поэтому мы к этому вернемся.

20-дневную коллекцию.

Коллекцию комментариев.

Comments.

Тут укажем также Comments.

И Create.

Так, создали Comments.

Дальше.

Создадим атрибуты.

У атрибутов, собственно, у нас будет непосредственно вот такие поля.

Давайте посмотрим.

Это текст и дил.

Связь со сделкой непосредственно.

Давайте текст.

Это текст самого комментария.

Ну, размер достаточно большой должен быть.

То есть, типа, я поставил бы 300, может быть, даже 400.

То есть, должен быть прям большой размер комментария.

Комментарии могут быть реально большие.

Обязательное поле у нас и create.

Дальше.

А дальше нам потребуется deal.

Вот это deal замечательный.

Давай создадим наш deal.

Relation.

Two-way relations указываем обязательно, да.

И указываем, с чем будет связь.

Ну, сейчас, короче, разберемся.

Two-way relations.

То есть, есть one-way.

Давайте объясню вам, да.

Это one-way, это в одну сторону.

Two-way, это в две стороны.

То есть, туда и обратно.

Соответственно, здесь мы указываем, с чем будет связь.

Deals.

Атрибут key, которое будет имя у нас непосредственно здесь Какое будет имя поля deals, допустим Даже я, наверное, в единственном числе напишу deal, потому что это одна сделка То есть у нас комментарий может быть прикреплен только к одной сделке, важно понимать То есть связь, точнее много комментариев к одной сделке, важно понимать, то есть один ко многим Дальше comments, это будет у соседней стороны, то есть у deal непосредственно, у сделки То есть у сделки может быть много комментариев

Связь непосредственно many to one получается, да, то есть comments может содержать одну сделку, а сделка может содержать много комментариев.

Вот это правильная история.

То есть связь у нас не one to many, как я сказал, а many to one.

И дальше, что важно, мы указываем, что будет происходить при удалении документа.

То есть это классическая история для базы данных.

При удалении документа может происходить разные вещи.

То есть, к примеру, мы можем назначать null, как здесь, чтобы значение было по умолчанию.

То есть, к примеру, удаляем сделку.

Что будет происходить с комментариями?

Либо мы их также чистим.

Либо мы им ставим null, чтобы они оставались в базе данных, но при этом они не были привязаны к никакой сделке.

В нашем случае я укажу здесь каскад, чтобы удалили все документы, потому что я считаю, что комментарий не имеет смысла без сделки, потому что комментарий относится к конкретной сделке.

И нажимаем Create.

Вот таким образом, на простом примере, я сказал, как работает связи у нас в AppWrite.

Дальше.

Теперь еще один атрибут.

Так, нет, в комментариях больше нет атрибутов.

Создаем новую коллекцию.

Называться у нас будет Customers.

Customers.

Здесь пишем Customers.

Create.

Customers у нас непосредственно будет Attributes.

Create Attribute.

Name, email, avatar.url и from source.

Давайте создадим.

Name и email будут обязательными.

Значит, name.

Размер будет, опять же, 40.

Required, да, обязательное поле.

Customers — это наши клиенты непосредственно.

Email, также 40.

Тоже required поле.

Create.

Дальше.

У нас идет avatar.url.

У него по умолчанию будет уже определенная картинка.

Давайте ее сейчас к себе скачаем для начала.

Вот, вы можете любую другую поставить картинку, особо здесь разницы нету, что вы будете ставить.

Так, давайте мы укажем аватар URL.

Для начала загрузим картинку, то есть идем в Storage, значит, Create Bucket, создаем, да.

Здесь пишем, опять же, тоже Storage, и здесь делаем тоже Storage.

Ну, я не знаю, на самом деле, как здесь правильно написать.

Я напишу так.

Давай здесь также напишем storage.

Storage.

Вот наши, кстати, агишники готовы уже к этому моменту.

Теперь дропнем сам файлик.

Create file.

Choose file.

И выбираем файлик.

Вот наш файлик по умолчанию.

Create.

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

Я укажу, что все, собственно, любой может читать файл, а любой юзер, all users, может и создавать, и обновлять, и удалять файл.

Update.

Все.

То есть, понимаете, да?

Чтобы у нас не было такого, что, допустим, люди не могут просмотреть файл.

То есть, все они могут смотреть файл, но редактировать его никто не может и удалять.

Это важно.

Ну, здесь другие есть настройки, это уже не касается этого ролика, скажем так.

Все, вот мы эту историю сделали.

Теперь возьмем ссылочку.

То есть как это можно сделать?

Ссылочку взять.

Перейдем сюда, файл URL и копию URL.

Так, посмотрим.

Да, копия работает.

Теперь перейдем назад.

Идем назад, назад, назад, назад, назад, назад.

Возвращаемся сюда.

Теперь create attribute string avatar URL.

Вот, здесь мы указываем размер Ну, так же даже пусть будет 50, наверное Мне кажется, что 50 будет окей

Только даже, знаешь, не стринга, я немножко неправильно сделал, а тут есть специальное такое поле URL.

Вот, нам даже оно нужно больше.

То есть здесь указываем аватар URL.

Дефолт value будет вот такое.

И, в принципе, все.

Сохраняем.

То есть по умолчанию у нас уже будет аватарка определенно стоять.

Дальше создаем еще новые атрибуты.

From source, то есть откуда нас нашли.

И дальше связь со сделкой.

Откуда нас нашли, размер может быть также 40.

Это необязательное поле.

Создаем.

И еще один атрибут, последний, это связь, relations.

Связь будет у нас, так, customer с этим будет, получается, two-way relations.

И тут, собственно, мы указываем, да, я укажу, что это будет связь со сделками именно.

У этого будет, значит, у customer будет сделки, даже одна сделка, да.

Я все-таки хочу сделать так, чтобы у одного customer была только одна сделка.

Вот, но вы можете у себя сделать и по другому слову, но от этого поменяется весь ваш код непосредственно, да.

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

Я сделал так, что одна компания имеет с нами одну сделку, одну большую сделку, поэтому я напишу deal, а там будут собраться, будут customers непосредственно.

Хотя, подожди, у нас связь немножко неправильно здесь шла, связь немножко неправильно была.

То есть смотри, у меня была связь money to one, это неправильно, я вам только что про другое сказал.

То есть customers могут иметь одну сделку, сделки могут иметь много customers.

Но по факту сделки...

Сделка, да, может иметь только один тоже, поэтому это будет one-to-one.

То есть, кастомер имеет одну сделку, сделка имеет один кастомер.

Вот так.

Тогда это кастомер вот так сделаем.

Все.

При удалении будет удаляться.

То есть, удаляться будет.

Create.

Хорошо, вот такие атрибуты у нас получились непосредственно у кастомера.

Так, нам осталось перейти в сделку.

Ну, по идее, там атрибут автомата подгрузить, потому что мы связали их вот так с двух сторон, и все отлично заработало.

Теперь у нас с вами, ребят, полностью готова база данных.

Есть коллекции полноценные, комментарии, напомню.

Давайте покажу еще раз, комментарии есть.

Есть кастомеры непосредственно, вот наши клиенты, есть сделки непосредственно, вот наши сделки.

И есть непосредственно авторизация, которая у нас пока что еще не рабочая.

Давайте перейдем в сейдинги и посмотрим, что по авторизации.

Значит, у нас есть метод авторизации, есть по номеру, по Magic URL, по GVT, по email паспорту и так далее.

Тут очень много всего можно подключить, вплоть до Яндекса, вплоть до Твича, что угодно подключать, кайфуйте и так далее.

Реально штука удобная.

Вот, значит, и теперь, да, хотелось в сеттинги перейти, это очень важно на самом деле, потому что в таких вот всяких сервисах, если вы это не настроите, вы потом будете не понимать, почему у вас не работает код Переходим в settings, дальше опускаемся ниже, и вот тут важный момент Опускаемся, опускаемся, точнее не здесь, а где он находится, давайте посмотрим

А может вообще не здесь, а может в самой базе данных.

Давайте посмотрим базу данных.

Выберем базу данных.

А вот если в базу данных зайдем и settings.

Или скорее всего в коллекции по отдельности.

Давай в кастомеров зайдем.

Да, да, я был прав.

В каждой коллекции по отдельности находится.

Допустим, зайдем в кастомеры, опускаемся ниже.

И тут мы указываем, собственно, кто может иметь доступ к кастомерам, кто может их редактировать.

Я укажу, что все юзеры.

Все юзеры могут редактировать, все делать.

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

Поэтому вы на свой вкус и цвет можете выставить права свои, которые только захотите.

Но потом имейте в виду, что когда у вас будет юзер авторизирован, важно, чтобы у него были те или иные права.

Так что этот момент не забывайте.

Значит, здесь будет «All users».

Так, так, так, так.

Также удаляем, да?

Точнее, выбираем.

И то же самое для... Так, мы для кого сейчас делали?

Так, для комментов осталось.

Комментарии тоже могут писать абсолютно любой юзер.

Давайте сделаем так, так, так, так.

Эта штука невероятно здесь удобно сделана.

Лучше, чем в страпе, допустим, в той же самой, поэтому я рекомендую.

Так, все, с этим понятно.

Так, это тоже понятно.

Теперь, теперь, теперь.

У нас в этом плане все яичнички подгрузились.

Все готово.

Ну, можно переходить к форме авторизации, наверное.

Мы app.write.config закрыли.

Теперь нам предстоит сделать саму авторизацию, поле входа и так далее.

Давайте этим займемся.

Мы до сих пор, до текущего момента, еще не создали ни одну страницу.

Прикиньте, у нас все работает вот так вот через app.view.

Давайте, пришло время создать нашу страничку, а здесь поменять наxtvelka на что-то другое.

А на что-то другое я имею в виду на next page непосредственно.

Пишем next page.

И теперь сюда вовнутрь будут грузиться наши, хотел сказать, картинки, наши страницы.

Те, которые будем сейчас указывать здесь.

Все, что касаемо роутинга.

Создаем новую папочку.

Пейджи сам называется.

И в пейджи у нас указываются все страницы.

Опять же, в начале ролика мы это разбирали в документации.

Но давайте сейчас мы с этим более подробно коснемся.

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

view.

Это наша главная страница.

Давайте я такую базовую накину в верточку для нашей страницы.

Какой-то скрипт, в котором будет очень много скриптов у нас в будущем, и view query, и так далее.

Template и наш HTML-код.

Здесь будет заголовок.

Я напишу, что это у нас будет, значит, я не знаю, мы на русском, вы писать не на русском, я вот пока что еще думаю, как нам лучше это сделать.

Давайте на русском будем писать Хотя опять же у нас сайт такой мультиязычный получается Давайте я напишу CRM System By Red Group Вот так я напишу Я не знаю, потому что как лучше писать То есть где-то я хочу на русском, чтобы было более понятно Где-то нет, короче так оставлю Все, это касаемо странички Теперь переходим, проверяем, обновляемся Должно заработать Вот видишь, все, CRM System By Red Group Все прекрасно работает

Окей, с этим я думаю понятен момент.

Так, что дальше?

А дальше нам нужно сделать страничку входа в систему.

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

Для этого мы должны кое-какие условия прописать.

Давайте этим тоже плавненько займемся.

Для начала сделаем саму страничку, пока что не опираясь на sidebar, просто отдельно.

New file, напишем login.login.view.

Это будет страничка входа.

Давайте я скопирую эту всю историю, хотя она мне потребуется, но тем не менее я скопирую.

Вот это закроем и тут будет наша страничка входа.

Значит, из базового каркаса я делаю flex-item-center-justify-content, минимальную высоту и ширину.

Это я хочу сделать просто такую рамку, и посередине будет формочка прямо по центру страницы.

И вот та самая формочка.

Я ее округляю, бэкграунд делаю как у сайтбара, ширина у нее 1,4, то есть там 25%, паддинги и, опять же, заголовок.

Посмотрим, как это выглядит.

Все, давайте перейдем в страничку логина.

Login, слэш, логин, и вот она, наша страничка логина.

Вот наша будущая форма.

Давайте инициализируем саму форму.

Смотрите, я в этом проекте, давайте делаем форму без экшена, я в этом проекте вам покажу специально две вариации реализации форм во Vue.js.

То есть одну покажу обычную, без использования сторонних библиотек, вот сейчас мы ее будем делать, а вторую покажу наоборот, с использованием сторонних библиотек, которые предоставляет hook.use.form.

Это называется Vvalidate библиотека.

То есть я попробую и так, и так, чтобы у вас было две вариации, как вы могли это все использовать.

Чтобы у вас был и такой путь, и такой путь.

Допустим, вдруг вы не хотите использовать дополнительные библиотеки.

Ну и все.

Теперь мы пришли к тому, что нам не хватает UI-компонентов.

Нам нужны поля.

Нам нужны поля, которых у нас нет.

Давайте их сгенерируем.

Для этого вспомним, где наша команда замечательная.

Так, опускаемся сюда и ищем нашу вот npx-shad-cn-view-lattice-dd-button.

Меняем button на input.

И генерируем наше поле непосредственно.

Ожидаем некоторое время.

Наше поле должно вот-вот появиться.

Вот оно и появляется уже.

Постепенно вырисовывается.

И все.

Все успешно у нас готово.

Теперь давайте его инициализируем сюда.

UI.

UI input.

И его вот таким вот инициализируем.

Значит, у него, во-первых, будет placeholder.

Как видите, смотрите, в чем прикол.

Вот вам прикол в UGS.

Из-за того, что магия, среда разработки не понимает, что происходит.

Опять же, в AppStorm, скорее всего, такого не будет.

Но вот в VSCode будете готовы с этим смириться.

Смотрите, вот я сейчас сделал компонент, да, и пишу, видишь place, а он не понимает, а что за place, а я хочу написать placeholder, а он его просто не видит.

Что в таком случае делается?

Идем command-shift-p, restart-extension-host, перезагружаем все экстенши на виз-коде, ждем какое-то время, и вуаля, ничего не работает, да, и вуаля, ничего не работает, подожди, а почему не работает?

Не, это заработало, нужно заново написать просто.

А, все равно это не работает.

Прикинь, все равно не работает.

Хотя он подгружает все верно.

Ну ладно, давай мы не будем тогда его слушать.

К сожалению, без автокомплита сегодня.

Placeholder.

Здесь у нас будет непосредственно e-mail.

E-mail.

Не забываем указать также type.

Type у нас будет также E-mail.

Сегодня без подсказок, ребят.

Это в UGS с этим все нормально, не парьтесь.

Так.

И класс я сделал MB3, то есть margin-bottom 3.

Давайте посмотрим, что вообще получилось у нас в целом.

Так, пока ничего не вижу здесь.

И давайте сразу, видишь, у нас, как сказать, у заголовка страницы нету title.

Как видишь, нету title.

Давай его назначим сразу же.

Тут два варианта, на самом деле, как видите.

Там можно, там по документации мы сегодня чекали, можно использовать либо useHead.

Вот таким образом, да, useHead.

Опять же, импортировать ничего не нужно, пишем title просто и пишем, допустим, логин.

Вот, title, логин указываем, смотрим сюда, обновляемся, и у нас вообще все сломалось.

Вообще прям полностью сломалось.

Давай мы его еще раз переобновим.

Что-то ему не нравится, что-то идет не так, боюсь, что нужно кэш было почистить.

Что-то он сразу мне не понравилось, как он заработал.

Он компонент не прочитал почему-то.

Вот, теперь все прочитал, теперь все работает.

Не знаю, это прикол в UGS, а точнее Naxt.

Не знаю, что с этим делать, честно вам признаюсь.

Но вот такая история.

И будьте внимательны, видите, у нас куча вордингов.

Потому что мы посоздавали ссылки на страницы, а самих страниц нет.

И в идеале вам их создать.

Вот, чтобы они просто не висели здесь, вам не ругались лишний раз.

Я думаю, это тоже сделаем, чтобы у нас просто вордингов не было лишних.

Как видим, тайтл подгрузился.

Вот вверху тайтл готов.

Вот, логин.

Можно, допустим, так написать, CRM System Вот, для красоты просто, вот можно так сделать, к примеру Можно еще отдельную функцию вынести, это все использовать, как хотите Вот, либо второй вариант делаем через UseSeoMeta Я не понял, честно, в чем их разница по документации Ну, то есть и так, и так работает Есть еще серверная часть, UseServerSeoMeta Но будьте внимательны, он работает только в том случае, если у вас компонент работает на серваке Если нет, то работать не будет

У нас приложение под клиент полностью заточено, поэтому серверная часть работы не будет Я оставлю, пожалуй, SEO-мета, пусть он будет висеть, и все, вот поле подгрузилось То есть, видишь, у нас в приложении такая ситуация, что поле пришлось нам очистить кэш, чтобы NUX заново все прочитал и все заработало Не знаю, вот такой баг нашего проекта Видишь, вот сейчас оно все работает, все подсказки работают

Теперь давайте, собственно, посмотрим, как во Vue.js у нас происходит двустороннее связывание.

То самое двустороннее связывание, почему хвалят Vue.js именно новички.

Потому что, напомню, чтобы в React нам связать поле, давайте объясню, что такое.

Смотрите, нам нужно как-то получить значение input.

Мы не можем, как в обычном JavaScript, просто сделать там document.getElementById и получить value.

Мы так сделать не можем.

И нам предстоит в таком случае, ну, типа, будет неудобно, короче.

То есть, ну, так делать нельзя.

Вот так я скажу.

Во framework так делать нельзя.

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

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

Это момент понятен.

Во Vue.js делается гораздо все проще.

Во Vue.js мы просто делаем двустороннее связывание.

То есть нам не нужно связывать руками эту всю историю.

Вы пишете лишь одну, там, условно, цитату, да, одну строчку, и у вас все уже работает.

Покажу, как это все обстоит во Vue.js.

Смотрите, значит, опускаемся ниже, пишем const email ref, ну, название на ваш вкус и цвет.

Дальше ref указываем, да, во Vue.js.

Опять же, ничего не нужно импортировать.

Опять же, по поводу импортов, насколько я понимаю, это только работает в Nuxt.

Если вы пишете на обычном view, у вас автоимпорты работать не будут.

Пишем ref и указываем по умолчанию пустое значение.

Все.

Теперь можно его спокойно связать.

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

Идем просто в input и через обычные классические...

ViewModel, мы просто делаем связь, вот такую, все, связь готова Опускаемся, пишем поле, вот оно наполняет спокойно И мы спокойно можем читать сразу же Допустим, давайте его послушаем, во View слушать поле можно с помощью email Слушаем, либо отслеживаем, другое слово здесь можно подобрать, да И давайте его будем читать, когда будет оно меняться, email.ref Теперь идем сюда, Inspect, смотрим консоль, открываем

Чистим эту всю историю.

Когда мы пишем, видишь, оно сразу же нам появляется.

Вот наше поле.

Во view будьте внимательны.

Видишь, оно в каком формате вам показывается?

Потому что view — это реактивное поле.

То есть оно не отдает вам напрямую значение, как в React.

И чтобы получить значение именно вот в этой части в скрипте, нужно писать .value.

При этом, если вы используете eml.ref вот здесь, в шаблоне, .value писать не нужно.

Сохраняем, обновляем.

Пишем что-нибудь здесь.

Как видишь, вот оно заполняется.

Все успешно, все спокойно работает.

Хорошо.

Касаемо этого понятно, я думаю.

Все, watch можно убрать.

По такому же принципу создадим другие поля.

У нас это будет поле еще параллельно password, password.ref и name.ref, поле для имени, name.ref.

Вот.

Также мы это все подублируем здесь.

Давайте раз, два и хватит.

Здесь будет у нас непосредственно password, password.

Здесь type тоже password.

тоже mb3, vmodel, тут уже делаем password, ну и таким образом заполняем другие поля, да, вот окней.

Окей, внизу делаем две кнопки рядом друг с другом, для этого делаем div общий, да, чтобы у нас был flex, и между ними было расстояние, с помощью гэпа делаем, и сами кнопки, ui-button, значит, первая кнопка это будет логин непосредственно, тут вариант указываем secondary, но у нас по умолчанию secondary, поэтому не имеет смысла его указывать, и важно указать type-button, type-button.

Вот, чтобы у нас они адекватно работали.

И дублируем, то же самое делаем для регистрации.

Потом мы сделаем на них событие клика.

Пока что мы к этому не возвращаемся.

Вот, все, вот наши кнопочки работают.

Вот, прекрасно все отрабатывает.

Как видите, у нас маленькие паддинги.

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

Вот логин, регистр, все прекрасно работает При наведении эффектов, к сожалению, нету по какой-то причине Их можно добавить, в принципе, если мы перейдем в button в index.ts И перейдем вот сюда, вот secondary При наведении у нас bg.secondary становится просто 80 Мне это вообще не нравится в корне Поэтому пусть будет при наведении Допустим, так Slate Подсказок здесь нет, конечно же, у нас Slate 500

Плохая история, не работает Давай мы сами прикинем, попробуем что-нибудь здесь прикинуть Примерный цвет какой-нибудь Так, так, так, так Где наш цвет замечательный?

Вот он, цвет наш замечательный Так, вернем сюда и чуть его подукрасим Вот таким, вот таким цветом я сделаю Это будет при наведении То есть это будет BG, вот такой цвет при наведении

Ну, опять же, мы не увидим изменений, потому что нужно очистить кэш.

Я не знаю, что с нашим происходит реально.

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

Что-то шляпа какая-то.

Мне кажется, Hot Reload не работает в каком-то смысле.

Не знаю, короче, вот, видишь, теперь работает, вот, только после обновления Так, оставим, как видите, у меня курсор Pointer по всему приложению работает, а баг, видимо, браузера, не знаю Особо не суть, скажем так, не суть Теперь давайте сделаем уже запрос к серверу, но перед этим сделаем проверку небольшую Нам нужно проверить, на самом деле, ввел ли человек в наши поля какие-то данные или нет Потому что иначе смысл отправить на сервер пустые поля

Смотрите, ребят, какая тема получается.

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

Значит, что я хочу сделать?

А я хочу сделать, короче, состояние, то есть наш стейт-менеджер, потому что...

Смотрите, нам важно хрень где-то юзера.

То есть мы могли бы условно, и возможно это излишне, то, что я сейчас буду делать.

Ну, во-первых, я хочу показать вам на практике, как работать со стейт-менеджером Pinia, потому что все его используют во Vue.js, и я думаю, что странно было бы сделать ролик про Nux и не использовать стейт-менеджера, потому что вам это нужно было для практики в ваших проектах.

И, возможно, это реально излишне.

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

То есть, можем взять наш app.write, сделать там, выполнить просто метод, который называется там, по-моему, getCurrentProfile, что-то в этом формате, и получить текущего юзера.

Как бы вроде бы все окей работает, да?

Но я хочу пойти дальше.

Я хочу все-таки записывать это все в наше локальное состояние.

Возможно, да, оно будет дублироваться.

Но при этом у вас будет практика, как нужно писать State Manager.

И при этом мы точно будем иметь свое собственное состояние у себя на клиенте.

И которое мы можем использовать в своих компонентах.

Поэтому давайте воспользуемся Pinia.

Давайте перейдем, ее сначала настроим.

Pinia на Extreme.

Давайте поставим ее сюда.

Так.

Смотрим здесь.

Пишем yarn etc.

И поставим пиня.

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

Я уже устал от этих редаксов всех.

Как вы знаете, я проект давно уже на редаксе не пишу.

Я пишу в основном на жуштанде, либо на ютай.

Уже устал от этих всех тяжеловесных, ненужных никому вот реально стейт-менеджеров.

Просто можно взять спокойно вот эту историю, да, пиня, и кайфовать, наслаждаться и так далее.

Дальше, что мы делаем?

Дальше мы, это нам не нужно делать, да, это мы пропускаем, это если вы npm используете, вам это нужно указать, вот, я использую ялт, поэтому не паримся, берем pinia.next, идем в наш любимый конфиг, то есть next.config, идем дальше вот сюда и вставляем наш pinia, pinia.next, сохраняем.

Далее, что мы делаем?

Опускаемся ниже, и тут написано, как вообще им пользоваться.

Ну, я думаю, что это нам уже излишне.

Нам некие провайдеры не нужны, в принципе.

Да, и мы можем уже... Я вам сам все покажу, как все будет обстоять.

Единственное, ребята, что я сделаю, это перейду в NuxConfig и кое-какую настройку еще поставлю.

Опустимся ниже.

Для пини я укажу, где будут храниться все наши сторы.

То есть наш стор глобальный.

Stores.dears и указываю, что сторы будут храниться в папке store, что логично.

Создадим эту папочку store.

У нас будет несколько сторов, то есть, ну, как бы один стор, несколько стейтов, скажем так, состояния.

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

Вообще, я говорю слово авторизация, и правильнее говорит слово аутентификация, но оно просто сложнее произносится, поэтому я говорю авторизация.

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

Но

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

Поэтому перейдем в Store, создадим наш новый Store.

Для начала идем в Store, напишем, что aufstore.ts, запускаем.

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

Поэтому опишем первый интерфейс.

Собственно, он будет очень простой, iaufstore,

e-mail, name и string, ой, и статус, да, это все, это вот базовые поля нашей авторизации, то есть мы имеем e-mail нашего непосредственно пользователя, который уже в системе, его имя и его статус, все, больше нам от него ничего не требуется, если вам нужно, добавьте сюда другие какие-то поля, но опять же вы можете к нему иметь доступ через базу данных в немножко ином виде, то есть, короче, я потом покажу эту историю.

Вот.

Статус будет себе хранить либо человек в системе, либо он не в системе.

То есть, либо он авторизирован, либо нет.

Вот и все.

Это будет статус у нас.

Мы его потом чуть-чуть поменяем, опять же.

Значит, по умолчанию какое будет состояние?

По умолчанию состояние будет следующее.

Мы берем default value, указываем, что тип у нас user в объекте, объект, в котором есть user, да, и в нем уже указываем, что у нас будет email пусто, name пусто, а статус false.

При этом и в таком положении мы понимаем, что по умолчанию авторизации у нас нет.

То есть, человек по умолчанию будет не авторизирован.

Вот это важно понимать.

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

Так что это мы чуть позже сделаем.

Теперь как делать сам Store и как вообще идет к нему обращение.

Мы создаем свой хук.

Пишем export const useAufStore.

Тут вызываем функцию defineStore для описания Store.

DefineStore.

Вот таким образом.

Видишь, импорты не работают, потому что мы в Vue.js и в Nuxt.

Я про это вам и говорил, что TypeScript работает здесь просто ужаснейшим образом.

Видишь, я не могу вызвать импорты.

Я не знаю, откуда брать и так далее.

Приходится идти и ручками все писать.

Хотя я, видимо, неправильно написал.

Подожди, тут, по-моему, не нужно его писать.

Давай мы поменяем add missing function.

Сейчас, подожди.

Так, стой.

Сейчас.

Define store, да.

Я define букву перепутал.

Define store.

Так, смотрим сюда.

А, все работает.

То есть, по-моему, молчание работает.

Окей, ладно, нагнал.

Извиняюсь, в этой ситуации нагнал.

Дальше мы указываем название нашего store, то есть ключ, авторизация.

И дальше мы указываем наш объект, в котором мы будем описывать то, что будет в нашем store.

Во-первых, описываем текущий state.

State

Он у нас будет по умолчанию вот такой, default.value.

Это то, что по умолчанию.

Стрелочная функция, отдаем default.value, вот этот default.value.

Все.

Здесь это наш state по умолчанию.

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

Понятно, да?

Мы это уже разобрали.

Дальше.

Мы можем тут описывать как и getter, так и action.

Что такое getter?

Все боятся этих слов, там, гетеры, сеттеры и так далее, в классах еще у нас-то было, все так боятся, новички не понимают, потому что запугали вот всякие там IT-блогеры, да, ну, вот этими словами сложными, непонятными и так далее.

Сейчас я вам объясню, что такое гетеры, что такое сеттеры.

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

Тоже все звучит максимально сложно.

На самом деле, когда ты на практике все это пробуешь, оно, на самом деле, очень просто.

Поэтому getter мы описываем тогда, когда нужно что-то получить.

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

Чтобы мне каждый раз не брать, допустим, user, там, user.status, там, равно, false,

Ну, я утрирую сейчас, да, ну примерно.

Чтобы мне каждый раз это не писать, я могу взять getter, который будет выполнять задачу за меня.

Давайте его сделаем.

Пишем getters.

Так, getters.

Указываем, что у меня будет.

Я хочу получать isAuf, это будет моим getter.

Здесь беру state.

И если state в boolean значение переводим с помощью двух восклицательных знаков, если у меня state.user.status,

это и будет являться нашей авторизацией, то есть мы переводим в boolean, то есть если он есть, то он как бы есть, да, и на самом деле в boolean можно не переводить на самом-то деле, да, если так быть честным, в boolean можно не переводить, оно и так будет работать, потому что и так является boolean, вот, а setter это то же самое, что, ну здесь setterов, по-моему, нету, да, это setter, да, здесь setterов нет, они по-другому здесь, они здесь больше как экшены выступают, setter это то же самое, что getter, столько getter мы получаем, а setter мы назначаем, мы обновляем какое-то значение в нашем классе, это является setterами,

Но в нашем случае это называется экшенами.

У меня будет два экшена.

Экшен для очистки нашего стейта, в котором мы берем this, то есть обращаемся к текущему this внутри объекта.

То есть this сейчас у нас обращается внимание к this внутри этого объекта.

Берем this.

И у него есть замечательный патч, который мы можем вызвать и тем самым поменять наш текущий store.

У нас будет default value.

То есть мы обратно возвращаемся к нашему значению по умолчанию.

Это будет экшен очистки.

Чтобы потом мы легко могли вызвать clear и очистить наш стейт.

Дальше мы берем set А set это для краски для более простого назначения Для более простого изменения нашего состояния Помните, вот принимать какой-то input, какие-то данные Которые должны быть по типу, как вот этот eye-off-store И тут все мы просто назначаем this

патч, также берем, только уже берем юзера непосредственно, да, юзер будет назначаться на input, все, вот, то есть мы просто с этим наше значение, еще раз, получаем input, который по типу, как вот наш store, вот эти поля мы получаем, и их потом перезаписываем, их обновляем, вот наш set, сейчас давайте так сделаем, поставьте на паузу, вот это все изучите, поставьте на паузу, сделайте такой же абсолютно, ну, чуть его изменив под концепцию, да,

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

Помните, я вам говорил, что у нас вот это есть лоудинг, да, помните, вот этот лоудинг?

Вот это, да, красивенький.

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

То есть, условно, у нас будет глобальный store, который мы могли контролировать.

Вот сделайте store для этого loading, который будет просто два иметь состояния.

True, false.

Все, boolean значение, true, false.

Вот поставьте на паузу и попробуйте сейчас разработать эту всю историю.

Вот так должно у вас получиться.

Use is loading store.

Я пишу обычно is всегда, да, чтобы было понятно, что это boolean значение.

Дальше называется он is loading.

State is loading true по умолчанию.

И при сете мы просто назначаем сюда boolean.

Все.

Максимально все примитивно и просто.

И потом мы еще с вами сделаем еще один store, но это будет чуть потом.

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

Потом мы его тоже разработаем, поэтому не забывайте про этот момент.

Здесь возьмем наш самый user's loading store, который мы уже только что инстализировали.

И опять же, как видишь, он его не видит.

Я не знаю, что у меня происходит.

Давай я попробую заново поставить наш проект.

Я сделал rm, rf, node-modules, почищу, да, и запущу yarn заново.

Переустановлю проект.

Возможно, что-то поменяется, потому что у нас какая-то, ну, лажа.

Видишь, нам приходится каждый раз сбрасывать кэш.

Когда я разрабатывал проект на Vue, вот первую версию, да, у меня такого не было ни разу.

То есть, да, нужно было иногда перезагружать, но это было там пару раз, скажем так.

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

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

Что-то как будто не работает.

Я не знаю почему.

Вроде бы в U мы все, ничего не выключали, все вроде бы работает.

Честно, не знаю.

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

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

Для нас это актуально, потому что у нас сервак очень долго отвечает.

И роутер.

Роутер для чего мы берем?

Роутер мы берем для того, чтобы мы в потенциале сделали редирект на другую страничку.

То есть, когда человек успешно вошел в систему, его нужно перенаправить успешно на главную страничку сайта.

Поэтому указываем роутер здесь.

Нужно сделать логин и регистрацию.

Именно полноценный функция, которая позволяет нам этим это все дело производить.

Давайте их создадим непосредственно.

Мы будем регистрацию делать и логин через AppWrite, напомню.

Давайте это позакрываем лишнее.

Вот сюда, да.

Вот наш AppWrite, который уже полностью у нас готов.

И уже готов принимать себе авторизацию.

Напомню, здесь можно авторизацию делать разными способами.

Как через e-mail пароль, так и анонимную, так и через телефон, Team Invites, Magic URL, GVT.

огромное количество разнообразия.

Там и Apple, и Amazon, и даже вплоть до российских есть, даже Яндекс.

То есть в этом плане все очень сильно круто, и мы можем использовать то, что вам будет по кайфу вообще.

И даже Stripe есть.

Так что, кому надо, чекайте.

Вот, кстати, наш сайт выглядит таким образом, да, почему-то.

Давайте сейчас мы его запустим.

Видимо, я его выключал вчера перед уходом.

Так, давайте перезапустим, посмотрим, что у нас по сайту сейчас.

Ну, хотя там у нас все стабильно, да, мы уже это все видели, тут все стабильно.

Так, давай мы напишем const login.

Я сделал стрелочную функцию, можно сделать и обычную функцию, разницы особо здесь нету.

Обязательно делаем ее асинхронной, потому что мы будем работать с сервером, да, и нам нужно отслеживать, точнее, ожидать ответа от сервера, поэтому нужно async await использовать.

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

Для этого мы создали с вами глобальный loadingStore.

Давайте его зададим из loadingStore.set.true.

Все, вот наш метод set, который мы указывали вчера, вот он, да, принимает себя boolean и просто его записывает в state.

Вот, из loadingStore.set.true.

Мне такой вариант записи больше нравится, чем, ну, можно было другим вариантом сделать, мне такой нравится лучше.

Больше, поэтому мы с вами и сделали экшены Теперь делаем await, ожидаем, да, от сервера И выполняем функцию, которую нам предоставляет app.write То есть делаем client, точнее не client, а аккаунт Точка, значит, create И вот видишь, да, есть create анонимная сессия, там email сессия, jvt, magic url, ooof, фон и так далее Мы с вами возьмем непосредственно email session И вводим сюда email и пароль У нас уже они подготовлены, email.ref и name.ref

Не name.ref, а password.ref.

Это касаемо полей.

Но опять же, они так не будут.

Нужно через value указывать, как я говорил уже до этого момента в этом ролике, что если вы используете здесь внутри реактивные переменные, нужно писать .value.

Но если вы их используете где-то вне в HTML, можно писать просто вот email.ref.

Дальше.

Мы хотим смотреть, что сделать.

Вот сейчас у нас уже сессия создается, все должно работать, все должно применяться и так далее.

Но мы хотим все-таки, как я и говорил, поработать немножко со State Manager, со своим локальным, да И хотим туда добавить состояние о том, что мы человека залогинили в систему Для этого нам нужно выполнить еще одну функцию к серверу для того, чтобы получить текущего юзера Пишем const response, то есть ответ, да Пишем await и указываем, значит, account, опять же, get session Вот, получаем текущую сессию

Прошу прощения, можно без get session, можно просто get написать, и тогда мы тоже получим сессию текущую.

Давайте вверху обозначим, вот у нас есть loading store, добавим сюда также наш store, который из авторизации.

Вот он, useAufStore.

Тут можно также его написать, в принципе, чтобы было понятно, что это именно он.

И он нам потребуется для записи.

Если ответ есть какой-то,

Если все-таки данные приходят, то мы должны записать это дело все в Store.

В Store, на самом деле, максимально просто записываем, берем Store, потом берем .set, наш любимый, и заполняем по очереди, там, email, name и status.

Я возьму все поля отсюда, response.email, response.name, response.status.

Все, это все, что у нас приходит.

Вот, тут даже написано userStatus, pass true for enabled, false for disabled.

Вот, то есть у нас все уже полноценно готово в этом плане.

В конце что нам нужно сделать?

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

Для этого мы выполняем email ref value, пустое значение, password тоже пустое значение, name тоже пустое значение.

Мы полностью все обнуляем.

Дальше делаем push.

Смотрите, push, вот так можно делать push, вот так.

Да, не многие из вас знают, но push является промисом на самом деле.

Некоторые новички думают, что push это не промис, но как видите, push является промисом.

Поэтому нам в идеале его дождаться.

И только потом выключить загрузчик непосредственно, потому что иначе это будет неправильно.

Мы дождемся, а потом уже выключим загрузчик.

Вот такая функция у нас получается.

Давайте еще раз разберем.

Значит, смотрите, есть состояние у нас, да, состояние наших полей, то есть там текущий email, пароль и имя.

То есть это вот те данные, которые мы ввели.

Есть глобальные сторы, глобальные хранилища состояния, скажем так, да, данных каких-то локальных.

Есть общая загрузчика, которая будет вешать лоадинг на весь сайт в состоянии загрузки.

И есть ауф-стор, который отвечает за текущую авторизацию на проекте.

Есть также роутер непосредственно.

Значит, здесь роутер нужен для пуша.

Дальше мы инициализируем функцию логин.

Вешаем сначала загрузчик на весь сайт.

В момент, пока грузится загрузчик, мы создаем сессию Create Email Session.

Дальше мы получаем текущий аккаунт, непосредственно его записывая в наш стор, чтобы спокойно к нему иметь доступ и чтобы немножко добавить практики по Street Manager'ам.

Дальше сюда мы назначаем пустоту для того, чтобы обнулить поля все.

И дальше мы делаем редирект, то есть перенос на главную страничку со странички авторизации и выключаем загрузчик.

Все.

Осталось теперь сделать регистрацию.

Давайте скопируем отсюда.

Здесь будет называться регистр.

Регистр.

Окей.

Сделаем await.

Опять же берем аккаунт.

Тут уже смотрите, если здесь мы брали аккаунт get, здесь брали аккаунт create email session, то здесь мы берем уже аккаунт create.

Нам нужно создать сначала аккаунт.

Вот, это для регистрации именно.

Тут передаем user ID какой-то определенный.

Опять же, как я и говорил, для этого буду использовать библиотечку uiid.

Ярн, это d, u, id.

Окей.

И также yarn add сразу же, минус d, types Сразу types поставим для этой библиотеки, она будет требовать Все, ставим библиотечку UID Так, быстренько ее сразу же импортируем

Опять же, мы могли бы использовать... Помните, мы с вами брали ID unique?

Вот этот вот ID unique.

Берешь ID, там у него точка unique есть метод.

Но, опять же, он у меня нестабильно работал почему-то.

Не знаю, может, потому что еще в бете находится сам app-write, и бывают какие-то баги.

Ну, короче, у меня прям ID-шник не генерировался именно, и я решил сделать его сам.

Опять же, они это сами пишут в документации, что вы можете либо наш использовать, либо свой.

Я буду использовать свой.

Вот тут мы пишем you... Так, давай.

From...

UID.

И тут мы указываем, по-моему, V4, да, мы берем.

Уже, кстати, V5 есть, нифига себе.

Я что-то и не туда.

Как V5 выглядит, кстати?

Может его воспользоваться?

Что-то я уже и не туда UID.

А ну-ка, V5, что это такое?

Что-то я как-то и забыл эту историю.

Так.

Ладно, не будем запаривать, сейчас тратим время на эту историю.

Короче, напишите в комментах, в чем отличие.

Вот, кто шарит.

Возьмем UID, четвертую версию.

Инициализируем его сразу же.

Он будет генерировать нам просто рандомную строку ID-шник.

Все, нам этого достаточно вполне.

Дальше принялся email, password и name.

Name, причем поле не обязательно.

Я бы вообще его в форме убрал на самом деле.

Но как кому нравится, скажем так.

Можете его оставить, в принципе.

Можно убрать для минимализма, можно оставить, тут уже как бы на ваш вкус и цвет.

Так, password.ref и name.ref.

Опять же, потом я покажу, как можно работать с формами немножко по-другому.

Когда мы будем делать добавление там карточки какой-то определенной, там будет чуть поинтереснее.

Но пока что вот таким образом.

И везде добавляем value, не забываем этот моментик.

Value и .value.

Отлично.

И после успешной регистрации, вот когда мы ожидаем, да, мы можем спокойно выполнить с вами логин.

То есть делаем await и выполняем функцию выше.

Просто логин выполняем.

Так, только логин смотрите.

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

Поэтому нам здесь даже просто можно вызвать опять же логин, у нас уже все данные есть без проблем Так что это очень удобно на самом деле Теперь у нас есть и регистры логин, осталось их привязать к конкретным кнопочкам Как идет привязка во Vue.js?

Через собаку, клик, тем самым вешайте событие какое-то определенное Я буду вешать сюда логин, а сюда я буду через собачку клик

Клик, вешать сюда регистр.

Регистр.

Отлично.

Сохраняемся.

Так, ну и по идее должно все сработать.

Мы, к сожалению, проверить сейчас прям полноценно не сможем.

Нам нужно еще кое-что поднастроить.

Но, тем не менее, предварительно можем посмотреть.

У нас, опять же, никого сейчас загрузчика не будет.

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

Эти warning мы уберем чуть позже.

Нужно просто страницу создать и все.

Давай, допустим, попробуем регистрацию произвести.

Пишем test.ru.

1, 2, 3, 4, 5, 6, 7, 8.

Здесь 8 символов нужно обязательно.

И, значит, напишем «акс».

Так, регистр.

Видишь, как долго сервак отвечает, то есть очень долго вот ждем, ждем, ждем, вот только сейчас запрос ушел.

И, кстати, нас переадресовало, да, я забыл про это сказать, мы же сделали редирект, поэтому нас переадресовало автоматически.

Смотрим сюда, видите, произошло три запроса, аж целых три запроса.

Значит, первый запрос у нас был на, собственно, да, посмотрим, какой он запрос.

Так, это на сессию, да, а это на аккаунт.

Значит, на создание аккаунта у нас был запрос.

В ответ мы получаем наш ID-шничек.

Вот, кстати, можно посмотреть, вот такой ID-шник я сгенерировал, видите, за счет UI ID.

Чуть выше, наверное, еще поднять нужно.

Ну, там, я думаю, плюс-минус вам видно, о чем идет речь.

Так, давай это уберем.

Случайно отрезок выбрал не тот.

Вот так, да?

Потом мы e-mail получаем сессию текущую.

Вот превью, вот наша сессия.

Он наш браузер говорит и так далее.

И дальше вот наш аккаунт непосредственно, да?

Наш аккаунт вот такой.

Еще раз мы его получаем.

Окей.

С этим, я думаю, понятненько все.

Запрос идет успешно, ну, как видите, передрессация тоже сработала.

Значит, дальше мы можем, опять же повторюсь, исходить из вот этого, постоянно использовать наш замечательный вот этот вот аккаунт Get, получать аккаунт, его использовать.

Вот, но можем пойти дальше, использовать наш Store.

Опять же, для того, чтобы мы попрактиковались немножечко со стейт-менеджером, да, использовать наш Store непосредственно.

Поэтому, ну, как бы, и такой, и такой вариант хорош.

Наверное, я бы нативку все-таки использовал, да, эту историю, да, которую он предоставляет.

Но, опять же, в рамках этого видео, так как это видео, ну, скорее всего, наверное, может быть последнее, ну, мало будет видео на канале по Vue.js и по Nux, поэтому я должен был охватить максимальное количество технологий рядом с ними.

То есть тот же Pinio я должен был использовать.

Поэтому я оставлю таким образом.

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

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

Загрузчик на страницу.

И я хочу также сделать так, чтобы у нас непосредственно сайт-бар, вот эта левая часть, не показывался, если человек авторизирован.

Потому что, точнее, если он не авторизирован, она показываться не должна.

Это только для тех, кто уже в системе.

Давайте сделаем эту историю.

Откроем дефолт здесь.

Импортируем все нужные нам моменты, основные, да, то есть это аккаунт app.write.

Вот аккаунт app.write.

Так, у нас все правильно, да, app.write здесь находится.

Опять же, это мы уже с вами делали.

Берем наш стор глобальный, этот и этот стор для отслеживания.

Берем роутер для редиректа.

чтобы если человек вдруг не авторизирован, мы могли переадресовать на страничку входа в систему.

И теперь что важно.

Я хочу, чтобы у нас действия определенные происходили при маунте компонента.

То есть у нас компоненты, как вообще в разработке, в SPA-приложении, как происходят.

Компоненты могут монтироваться и размонтироваться.

То есть когда они вот инициализируются, это называется монтирование компонента.

Так он появляется.

И вот нам нужно как раз при этом событии отслеживать нашу авторизацию.

Поэтому пишем событие onMounted.

То есть это аналог в реакции юз эффекта При монтировании, да?

Так, здесь переборочило немножечко Тут не забываем асинхронность добавить, асинг, да?

И не забываем также добавить try-catch Для отлова ошибок И finally обязательно, finally Значит, finally мы сразу же выключаем наш загрузчик То есть из loading store false Глобальный, да?

Ну, опять же, мы это уже делали, поэтому здесь, думаю, объяснение не требуется лишнее

Давайте сейчас посмотрим наш сторм.

Да, мы видим, что по умолчанию у нас загрузчик уже включен изначально.

То есть нам его нужно только выключить, поэтому он уже изначально у нас будет включен.

Так.

Выключить.

Дальше здесь при ошибке мы сразу же выводим роутер push и на страничку авторизации, потому что это логично.

Если ошибка выпала, значит человек не в системе.

И тут мы выполняем сам запрос.

Получаем нашего юзера с помощью того, что мы уже делали await account get.

Получаем нашего юзера.

И если юзер есть, то мы записываем его в store.

store.set нашего юзера.

Вот таким образом.

Посмотрим сюда.

Да, будет правильно вот так записать его без фигурных скобок.

Сохраним.

Еще раз, давайте посмотрим, что мы проделали.

Взяли два store глобальных для загрузки, общего загрузка экрана нашего, для store авторизации непосредственно, для редиректора роутера взяли.

Дальше что мы делаем?

Мы с вами при монтировании компонента выполняем асинхронную функцию, стрелочную.

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

Если ошибка, редиректим на логин пейдж, если нет, то тогда...

Мы с вами получаем нашего текущего юзера и спокойненько записываем его в наш замечательный стор.

Теперь, помните, мы с вами делали loader.

Вот он наш loader, очень такой простенький, примитивный и так далее.

Можно, кстати, nuxt сюда повесить, nuxt image можно повесить без проблем.

Он будет кэшировать, будет чуть-чуть получше на самом деле.

Давайте оставим его таким образом.

И давайте его инициализируем непосредственно наш layout loader.

Значит, он будет инициализироваться здесь.

Вот так мы его откроем.

Дальше, видишь, мы можем без импортов за счет правильного нестинга, так называемого.

Значит, пишем, если, ставим условие, да, то есть, как у нас условие пишется во view, то есть, если какое-то условие есть, компонент показываем, если нет, компонент не показываем.

Директива view, отлично.

Если у нас изloadingstore.com,

из loading, да, если он у нас есть, тогда мы показываем loader, что логично.

В другом случае, вот тут ставим v else, тогда мы показываем вот нашу секцию непосредственно.

Мы пойдем еще дальше, нам нужен вот этот вот класс grid замечательный, да, показывать только в том случае, если у нас, точнее, отображать в том случае, если у нас есть sidebar.

Если sidebar нет, он нам не нужен.

Как проверить, sidebar есть или нет?

Sidebar у нас будет только в том случае, если у нас есть непосредственно вход в систему.

Давайте мы инстализируем, как делается класс вообще, как кондишн сравнения классов, да, или условия в классах.

Ставим двоеточек обычно.

Здесь синтаксис просто, на мой взгляд, ужаснейший, да, и, короче, немножко непривычный для тех, кто там React пишет и так далее.

Значит, мы ставим двоеточие, потом внутри двойных кавычек пишем фигурные скобки.

фигурные скобки, и в них уже пишем непосредственно объект, то есть grid, в случае, если store.isAuth, вот, помните наш гетер, который мы с вами делали, вот, это наш гетер замечательный, и вот здесь он как раз-таки пригодился, если авторизация есть, мы можем grid применять, вот такая интересная история, значит, касаемо дальше, сайдбары, сайдбары нам нужно тоже показывать не всегда, да, а показывать только в том случае, если у нас присутствует, конечно же, авторизация, поэтому все очень просто, пишем vif,

И также делаем store.isAuf.

Вот таким образом.

Сохраняемся.

Отлично.

Идем назад.

Обновляемся.

Сейчас должно все... Как видите, загрузчик идет, но он очень маленький.

Очень маленький загрузчик.

Да, посмотрим, как его нам покрупнее сделать.

Так.

Лодинг, лодер, тогда покрупнее сделать, как-нибудь его попробовать, ширина 300 пикселей, ну-ка, вот, сейчас уже получше, да, поменьше чуть поставим, 240, я думаю, что вполне себе, 220 я оставлю и пойдет.

Так, смотрим.

Вроде неплохо смотрится.

То есть, видишь, идет загрузка сначала, да, потом загружается наш проект.

Давай выйдем из системы.

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

Давайте его напишем.

Так, здесь, я думаю, понятно.

Опять же, это мы разобрали подробно.

Тут понятно, да, что мы делаем просто проверку.

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

Вот этот загрузчик мы его ставим.

Если же у нас нет, то мы показываем просто обычно нашу секцию, вот эту всю секцию.

И, собственно, дальше условия для авторизации.

Если человек в системе, мы можем показывать ему сайдбар и добавлять класс grid для верстки.

Сохраняемся.

Теперь пойдем, сходим в наш еще раз сайдбарчик.

Сайдбарчик, вот сайдбарчик, да.

И в сайдбаре вот мы допишем здесь непосредственно этот логаут, выход из системы.

Создадим функцию const logout.

Она будет отвечать за выход из системы, логаут.

Async.

Выполняем функцию.

Это функция для выхода из системы.

Мы сразу же вешаем на наш button.

Собачка click.

Logout.

Это кнопка выхода из системы непосредственно.

Давайте импортируем все нужные нам store.

Вот таким образом у нас будет store.

Глобально опять use of store.

У нас будет опять же router.

Use router.

И будет еще один глобальный store для загрузки.

Я вот так поменяю местами.

Чуть мне так больше нравится.

Теперь в логауте что мы делаем?

Вешаем загрузчик.

Set.

True.

И в конце вешаем false сразу же.

Тут выполняем await account.delete session.

Удаляем сессию непосредственно.

И указываем здесь session id.

А session id у нас все очень просто называется current.

Вот текущая сессия.

Дальше мы очищаем наш store.

Store clear.

И делаем redirect.

То есть await router push на страничку авторизации непосредственно.

Вот.

Сохраняем.

Итак, что мы еще раз с вами сделали?

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

Вот, в принципе, и все, весь функционал.

Давайте протестируем.

Так, выходим из системы.

Загрузка.

Page not found.

Я неправильно указал, потому что не ауф, а логин наследил страничка.

Так, логин.

Ну, как видите, все работает, сайтбара мы больше не видим.

То есть все, у нас прекрасно все отрабатывает.

Вот, нашего сайтбара мы не видим.

Обновляемся, видишь, ошибка, потому что мы пытались получить доступ к аккаунту, как я и говорил, да, но аккаунта у нас нету, и из-за этого нас переадресовало сюда.

Здесь все правильно.

Войдем обратно в систему.

Теперь через логин уже.

3, 4, 5, 6, 7, 8.

Логин.

Грузится, грузится.

И вот загрузилась.

Красота.

Я думаю, что да.

Все работает.

Еще раз давайте логат попроверим.

Все, вышли из системы.

Ну, то есть конфетка.

Вообще идеально.

Так.

Так.

Так.

Отлично.

Все, с этим, я думаю, мы порешали.

Так.

Теперь нам предстоит, ребята, делать самое, наверное, интересное в этом проекте.

Ну, точнее, много чего еще будет интересного.

Это непосредственно сам борт.

Сам вот этот вот канбанчик, саму вот эту сеточку определенную, колонку, как хотите называйте, именно наши все карточки.

Красивенькие, нарядненькие и так далее.

Будет интересно в любом случае.

Будет уникально, экстремально.

Я думаю, что вы насладитесь и кайфанете.

Поэтому присаживайтесь поудобнее, ребят.

Все самое интересное только начинается.

Так.

Не забывайте, ребят, я буду говорить об этом часто Потому что, ну, вдруг кто-то забыл проставить лайк, ребят Я думаю, что это того точно стоит Я трачу очень много времени Очень много здоровья на это трачу Постоянно аудитория знает об этом И все для вас, ребят Надеюсь, вам реально понравится И вы оцените этот ролик по моим стараниям, скажем так То есть по уровню моих стараний Так что пишите комменты и ставьте лайки Будет очень полезно, если мы ролик продвинем в рекомендации Это будет прям лучшая благодарность для меня И лучшая мотивация для продолжения этого контента

Теперь мы с вами, у нас есть layout.ui, layouts.pages, public.

Так, давай мы что с тобой попробуем сделать.

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

Нам нужна как бы и карточка вроде для вот этой вот истории, да, нужна сама карточка, UI-компонент.

А вроде бы и колонки хочется сделать.

То есть и так, и так хочется поступить.

Давай мы, наверное, начнем, пожалуй, с карточки, как ни странно.

Давай мы ее сгенерируем быстренько, ее немножко поднастроим.

Так, у нас, по-моему, было где-то здесь, еще раз поищем, поищем.

Так, у нас есть, вот, npx-shad-cn-view-latest-edd, только уже не input, а карт.

Инициализируем, да, посмотрим, как он выглядит, карт наш замечательный.

Shade CN View.

Посмотрим.

Documentation.

И вот здесь где-то карточка, вот наша карт.

Вот так она выглядит, наша карточка.

Мы ее воспользуемся ей.

Точнее, наша выглядит по-другому, у нас New York Style.

Вот таким образом.

И, собственно, ей мы сейчас будем пользоваться.

Нам предоставляют сразу и карт, и карт-хедер, и карт-тайтл, и карт-дескрипшн, и карт-контент.

Короче, очень много всяких карточек, а именно полей нашей карточки.

Очень прикольная темка.

Давайте ее как раз сейчас попробуем запустить в своем проекте.

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

Я сделал index view, вот, и пока что сделаем такую одну карточку.

Давайте div сделаем, uicard.

Так, ну и опять же он вишни видит, то есть нам придется сейчас перезагружать эти, ну вы поняли, что...

Чистить кэш, короче, и перезагружать.

Короче, у нас ничего не поменялось.

То есть, проблема в том, может, как-то мы неправильно поставили ShadCN, я не знаю, может быть, что-то проблема в нем какая-то.

Давайте сейчас перейдем, я, может быть, реально что-то упустил в какой-то момент.

Еще раз.

Значит, next.

Мы с вами что делаем?

Значит, это у нас есть TypeScript, это у нас точно есть, это у нас точно есть.

Здесь мы описываем tailwinds.shadc.next, run.sli, тоже сделали, здесь все поставили, ну как бы все, должно все работать.

Как будто из-за того, что помните, вначале там как-то кривовато поставил немножечко, может быть поэтому, а, ну видишь, у него utilset по-другому файлик называется.

Ну, опять же, мы потом в конфиге поменяли, короче, не знаю, ребят Если что, попробуйте при установке просто вот в этом элементе оставить по умолчанию, не менять, как мы поменяли Возможно, из-за этого какой-то баг произошел, я сейчас не буду переустанавливать, потому что это не имеет смысла, мы уже много чего сделали Но у нас сейчас, видишь, какой баг происходит, у нас при изменении самого компонента, да, приходится очищать кэш в самом NUX, иначе работать не будет

И это, конечно, не так комфортно.

Мы сейчас будем менять сам карт, а мы изменений видеть не будем.

Это, конечно, не так весело, скажем так.

Так, откроем наш карт.

Давай сейчас его инициализируем сначала, посмотрим, как он выглядит на текущий момент.

Значит, на текущий момент он выглядит следующим образом.

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

Ну, здесь максимально базовая.

Я сделал margin-bottom 3, чтобы между ними был отступ небольшой.

Добавил draggable свойства.

Напомню, сам момент перетаскивания мы будем реализовывать на чистом JavaScript.

Без единой библиотечки, по той причине, что во Vue.js очень плохие библиотеки.

Вот именно вот такого формата.

Если в React, допустим, есть dndkit, который замечательный и крутой, во Vue.js такого нету.

И все, что я переправил в своей библиотеке, они работали просто ужасно.

Никакой типизации нет и так далее.

Зато нативка, которую будем использовать, то есть нативный JavaScript, нативный HTML, нам позволит добавить типизацию, и все будет комфортно и красиво.

Да, может быть, будет не так красиво, но функционал будет такой же абсолютно.

Поэтому я ставлю «dragable true».

Дальше мы UI Card Header.

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

Content и Footer.

Тут будет дата нашей сделки.

Вот, можно посмотреть под наши карточки.

Вот она, замечательная, да?

Это название, это компания и дата сделки.

Ну, еще будет также стоимость вот здесь у нас указана.

Сохраним и посмотрим, что у нас получается вообще в целом.

Так, вот наша карточка уже инициализирована.

При этом выглядит достаточно неплохо, выглядит в принципе более-менее, но опять же, как видишь, отличается по стилю немножечко, рамки не должно быть и так далее.

Поэтому перейдем в UI-карты, немножко его стилизуем.

Тут все очень просто, мы берем класс Shadow и вешаем свои классы.

Добавляем анимацию, мы сейчас класс этот внизу инстализируем, потом добавляем Border, непосредственно я отсюда его уберу, Border.

Border Transparent по умолчанию, Transition Colors, чтобы было плавное свечение, да, и при наведении будет подсвечен небольшой такой слабенький фиолетовый цвет, чтобы это было просто приятно и красиво смотрелось.

И Rounded не XL, а LG, этот же момент не забываем.

Дальше, внизу.

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

Вы просили верстки поменьше, поэтому прислушаюсь к вашему мнению.

Смотрите, анимация, вот она так выглядит, класс Animation, Animation, Show, 300 миллисекунд и Easy In Out.

Что мы делаем в самой анимации?

Смотрите, From To, как работает анимация, да, то есть откуда, куда мы идем, то есть это значение 0%, это 100%, это 90% посередине.

Значит, откуда мы начинаем?

Начинаем мы оттуда, что у нас есть такой бордер, у нас есть такие значения, то есть он немножечко меньше, да, 50%, скажем так, и он немножечко поднят кверху, чуть задрата кверху, и у него прозрачность 40%.

Потом на 90% я уже делаю рамку более светлую, чтобы у нас был такой эффект, как бы при перетаскивании рамка осталась, а потом она пропадала.

Я захотел такую штуку сделать, можно убрать в принципе.

И в конце мы рамку уже полностью убираем, при этом transform мы назначаем уже обычный масштаб и двигаем его к обычному местоположению, также назначаем opacity.

Как это работает, давайте проверим.

Сначала я очищу кэш.

У нас автообновление не работает именно этих компонентов.

И как видишь, при загрузке, смотри еще раз, видишь, красиво появляется.

Вот.

Все.

Вот это мне стоило добиться.

Видишь, при наведении красиво рамочка появляется.

Она не супер заметна, но она очень приятная.

Так что вот такую штуку.

При этом, видишь, он является драговым, можем его перетаскивать спокойно.

Так что в этом плане мы свою задачу решили.

Все, карточка у нас готова, можно идти дальше.

Итак, начнем с базы.

Назначим сначала title.

Да, title home.

Я напишу здесь title home CRM system.

Ну, чтобы просто как-то было покрасивее.

Так, дальше.

Куча всяких импортов нужно, в том числе давайте поставим библиотеку Date.js для форматирования даты.

Видишь, у нас дата в таком формате.

Значит, yarn.edd.date.js.

Очень крутая библиотека.

Она, в отличие от Moment.js, которая раньше была на хайпе, она более легковесная.

Я ее крайне рекомендую всем использовать.

Она очень крутая.

То есть вы там дату прокидываете и любой формат можете указывать.

Максимально удобная библиотека в этом плане.

Теперь смотрите, значит, что нам нужно сделать?

Нам нужно сделать момент, как я, давайте сначала опишу, как я хочу сделать эту всю логику.

У нас с вами есть колонки, да, то есть, допустим, там, входящие на согласование, это колонки статусов непосредственно.

И есть карточки.

И нам самое ключевое, нужно при получении данных расфасовать правильно карточки по колонкам.

То есть, в зависимости от статуса карточки, ее нужно выкинуть в нужную нам колонку.

Это очень сложно делается, я вас научу, как это делать, без проблем.

Но вот это нам нужно будет сделать.

Дальше что нам еще требуется?

Дальше нам еще требуется загрузить данные с сервера.

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

То есть важно, чтобы была полная синхронность с бэкэндом.

Поэтому я в угоду, скажем так, красивости, в угоду дизайну определенному Мы сделаем полную связь с сервером То есть мы делаем запрос на сервак Дальше у нас происходит мутация с помощью view query Что такое мутация?

Это отправка запросов update, post, delete на сервер Отправляем запрос, потом мы его получаем И при получении с помощью view query, опять же, мы с помощью рефетча

Перезапрашиваем новые данные.

То есть вот это, смотрите, у нас есть сетка определенная.

Мы получили вот эту сетку, эти карточки в начале загрузки.

Они определенного у нас даты обновления, скажем так.

Потом мы добавили карточку.

Мы хотим получить данные уже свежие с новой карточкой.

Мы делаем рефетч и получаем уже новую пачку данных.

Их просто вставляем.

Вот такая концепция нам нужна.

Вот такую концепцию мы с вами будем реализовывать.

И при этом нам важно хранить состояние.

Состояние будет чего?

Карточки, которые мы будем передвигать непосредственно, чтобы мы могли ее в базу данных потом кинуть.

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

Сейчас на практике будет вам более-менее все еще лучше понятно, так что давайте создавать.

Но перед созданием давайте мы с вами еще кое-чем займемся.

Перейдем в компонент, создадим папочку kanban.

New folder, kanban.

Дальше новый файлик kanban.types.

И в kanban, в types.ts мы опишем все типы, которые нам потребуются в нашем проекте.

А типов много, и я на самом деле, ребята, их сейчас описывать не буду.

То есть я просто вам их вставлю и расскажу про них лучше, потому что иначе в них нет никакого смысла.

То есть мы на это время очень много сейчас потратим, которое на самом деле нам и не нужно тратить.

Так, сейчас я вам скажу, что у нас по этим input непосредственно.

Значит, смотрите.

По интерфейсам.

Я вам уже рассказал, как их нужно описывать, да, и что это вообще такое.

Давайте разберем их более детально.

Смотрите, тут все очень просто на самом деле, да.

Интерфейсы я брал, исходя из того, что мы имеем уже в самой базе данных.

То, что нам отдает база данных, немножечко я их переизменил, да.

Что мы имеем?

Смотрите, карточка, это мы описываем интерфейс вот этой карточки справа, вот этой карточки мы описываем ее интерфейс.

Приходит ID-шник, name, то есть название, да, price, дата создания, company name и статус.

Статусы, как видишь, это у нас является Enum статусом Но опять же, Enum статус не для этого статуса, а для этого статуса Сейчас объясню почему Вот, и дальше интерфейс еще колонки Это вот конкретная колонка, вот раз колонка, два колонка и так далее Заметьте, что у колонки нет цвета определенного, да Мы цвет назначаем непосредственно за счет градиент, за счет прекрасной функции, которую мы с вами напишем То есть цвет у нас нету, какого статичного цвета, мы его прокидывать не будем Он за счет градиента и автоматизации

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

Название, то есть по-русски то, что мы пишем, и items, сами элементы внутри этой колонки, куда мы их и будем заносить после успешного получения с нашей базы данных.

И в конце, как видишь, у нас не хватает еще немножко types, давай их создадим также.

New folder types.

Дальше здесь создадим deals.

Deals — это сделки.

Types.ts.

И сразу инициализируем сделки.

Давайте я вам тоже их расскажу.

Значит, мы создаем интерфейс сначала BaseField.

BaseField — это базовые поля определенные.

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

Они будут везде у каждой сущности.

Поэтому я укажу просто, что это являются базовыми полями.

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

Дальше, описываем интерфейс кастомера, кастомер это наш клиент непосредственно, вот, допустим, компания OOG, у нее будет имя, у нее будет email, то есть, ну, email, да, электронная почта, аватар URL у нее будет и from source, как видите, необязательное поле, потому что это реально необязательное поле.

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

И дальше расширяем с помощью Extents на основе вот этого BaseField.

Дальше.

Комментарии.

Все то же самое.

Вот эти два поля плюс текст, то есть сообщение самого комментария.

В Enum статусе у нас есть ToDo.

Но смотрите, будем объективны, в комментах там еще есть одно поле.

Да, это связь как раз-таки непосредственно с карточкой.

Но здесь его описывать нет смысла, мы его даже не будем использовать на фронте.

Мы на фронте будем по-другому получать карточки, и нам здесь это поле абсолютно не нужно.

Но оно как бы формально присутствует.

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

Что могу сказать про енамы?

Енамы это зарезервированные слова, определенные строки, которые можно потом переиспользовать и ничего кроме этих строк мы больше юзать не можем.

Это нам идеально подходит.

У нас есть 5 основных Enum статусов, которые нужно сюда зарезервировать, которые есть также в базе данных.

Мы их опять же описывали, когда создавали BD.

Это ToDo, ToBeAgreed, InProgress, Produced и Done.

Это основные 5 статусов, которые нам нужно использовать.

Дальше.

Интерфейс Ideal.

Мы его экстендим на основе базовых филдов, опять же, добавляем ему create и так далее.

Вот, create add и ID добавляем ему.

А дальше, а дальше, да, мы непосредственно добавляем свой комментарий.

У каждой сделки будет свой массив комментариев.

У каждой сделки также будет один кастомер, только мы, смотрите, кастомер меняем на один кастомер, да, это ошибка у меня была.

Так, мы больше не ошибились, вроде бы нет.

Name – название сделки, price – цена сделки, number – значение в числовом формате, и status – это вот наш enum-статус непосредственно.

Все, сохраняемся.

Идем сюда.

Здесь types должны подгрузиться, опять же.

Видишь, TypeScript внизу ошибка.

Вот такая часто во Vue.js ошибка.

TypeScript server error.

Все, он не понимает, что происходит.

Поэтому заходим сюда.

Restart extension host.

Все.

Ну, то есть, приходится перезагружать каждый раз, чтобы оно заработало.

Опять ошибка остается.

Надеюсь, она пропадет как-нибудь.

Так, сохраняемся.

Все, обязательно, ребята, когда вы делаете импорт тайпов, реально тайпов, либо интерфейсов, чего угодно, пишите type здесь, да, потому что это правило нового TypeScript, да, и это важно делать, чтобы у вас была лучшая оптимизация, чтобы ваше приложение понимало, что вы импортируете именно type, а не просто какую-то переменную и так далее.

Сохраняем, отлично.

С тайпами мы закончили.

Теперь создадим состояние.

Мы это дело уже с вами умеем, да, состояние создавать.

Поэтому создадим два состояния.

Это будет const dragCard.

То есть, я еще, знаешь, добавлю здесь ref.

Я люблю добавлять ref, потому что было понятно во Vue.js, что мы работаем с реактивной переменной.

Значит, dragCard — это, опять же, карточка, которую мы в моменте перетаскиваем, которую мы взяли и пытаемся перетащить.

Это является нашим dragCard.

У нее тип будет iCard.

А здесь будет тип iColumn, то есть iCard либо Null, когда мы еще ничего не перетащили.

А тут Source Column, то есть тоже мы назначаем в момент перетаскивания, то есть это изначально исходная колонка нашей карточки.

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

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

Делать компонент слишком раздутым, хоть и Vue.js за счет своей архитектуры это позволяет делать, у них все в одном месте, и скрипты, и темплей, и так далее.

Но тем не менее, я пришел с React, я привык все декомпозировать, разделять отдельно.

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

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

Значит, для этого мы пишем useKanbanQuery, это будет наш хук замечательный, он будет в папочке Kanban, new useKanbanQuery.ts, вот, здесь будет хук наш создаваться непосредственно.

Итак, продолжаем.

Собственно, нам нужно сделать Kanban queries, запрос к базе данных на получение всего нашего Kanban, всех наших сделок.

Но прежде чем мы сюда перейдем, нам нужно создать Kanban дату.

Сейчас объясню для чего.

Для того, чтобы мы в нее с вами положили все наши колонки непосредственно, описали все наши колонки.

Давайте это быстренько реализуем.

Возьмем нужный нам type, нам понадобится enum-status и i-column.

Теперь мы экспортим.

Export const kanban-data, я так напишу.

И двоеточие.

Это будут у нас колонки в multiple моде.

То есть это будет массив.

Делаем так, делаем так.

Ну и описываем, собственно.

Давайте опишем первую колонку.

ID у нее будет enum.status.toDo.

Name у нее будет входящие.

Входящие.

Дальше items у нее будет пусто.

То есть items мы будем наполнять уже по мере получения данных.

Получим данные, будем заносить сюда item.

Пока что item будет пустым.

Сохраним и добавим таким же образом другие колонки.

На согласовании, в производстве, произведено и к отгрузке.

Всего будет 5 статусов, 5 колонок.

Вот таким образом.

На согласовании будет enum.status.toBeGrid, в производстве enum.status.inProgress, произведено enum.status.produced, и здесь done.

Понимаете, да, почему мы здесь пишем через точку, а здесь пишем в квадратных скобках?

Потому что через точку можно писать все, что является одним словом, да, либо через нижнее подчеркивание.

Здесь же мы пишем тогда, если это как-то строка определенная, да, условная.

То, что мы не можем через точку написать, тогда пишем через квадратные.

Просто, чтобы вы, допустим, знали.

Все, сохраняемся, с этим закончили.

Теперь идем в запрос.

По факту мы будем возвращать все, что возвращает нам U-Square.

А U-Square у нас пока тоже не может быть сделан.

Я не знаю, почему у меня идет импорт из Apollo, я вот это тоже прикол не понял, когда у меня Apollo нет в проекте, Apollo у меня в другом проекте.

Я не знаю, что за баг системы такой, но у меня Apollo в этом проекте нет.

Вот, нам потребуется Vue Query, давай зайдем в Vue Query, я про нее немножко расскажу и поделюсь, почему мы ее используем.

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

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

Вот, Inject Query все также работает.

И даже они для солида сделали, там то же самое, что для реакта, в принципе, да, create query.

А для свелта они тоже, по-моему, сделали, да?

Да, для свелта даже подготовили.

Молодцы, развиваются ребята.

Я помню, когда мы начинали использовать React Query, тогда он еще был, по-моему, второй версией.

И это, конечно, было прям забавно.

Тогда очень было все по-другому.

Давайте я вам объясню, почему используем Vue Query.

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

Почему используем именно TanStack Query.

Все очень просто, ребята, потому что это облегчает нам жизнь.

Раньше нам приходилось получать данные, потом выводить состояние для загрузки, для ошибки, для получения даты, все это записывать в локальный стейт, то есть работать с тем же самым редакцией в React, либо во Vue, там какой-то другой был стейт-менеджер, я не помню, как он назывался, не суть.

То есть нам приходилось работать со стейт-менеджером, создавать свои локальные состояния, и это было супер неудобно сделать.

Сделано.

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

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

Вот, штука реально крутая.

Давайте ее установим.

Она максимально просто работает.

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

Но, тем не менее, штука очень крутая.

Давайте ее поставим.

yarn.ddd.unstuck.view.query.

Не забываем, нужно один моментик сделать для Nuxt.

Для Nuxt она ставится чуть по-другому.

Сейчас вы мне вспомните этот моментик.

Давайте мы опустимся чуть ниже.

Как видим, для Vue.js подключается через импорт и app.use.

Для, опять же, ну и вот так оно инициализируется, собственно.

Как видишь, да, то есть мы берем useQuery, а вот видите, можем забрать уже и дату, и когда ошибка есть, либо нет, и саму ошибку, и состояние загрузки, состояние ожидания загрузки.

Короче, все это присутствует у нас.

Все очень круто.

И также типизируется.

Но, как я и сказал, нам нужен NUXT.

Давайте опустим чуть ниже.

Вот тут, кстати, для тех, кто не понимает, в чем отличие, можно посмотреть, является это заменой PIN и VUX или нет.

Можете почитать, поизучать.

Я хочу перейти SSR NUXT.

Вот сюда переходим.

Здесь нужно кое-что сделать.

Так, это, во-первых, у нас идет описание плагина непосредственно самого, да, viewquery.ts.

Да, поэтому пойдем сейчас создадим с вами next, да, идем сюда в next, вот, создадим папочку, называться она будет plugins, это, опять же, зарезервированное слово, которое позволяет вам описать плагины, которые в дальнейшем будут сами использоваться автоматически.

New file, viewquery, viewquery.ts.

И вставляем сюда.

Здесь, если вкратце пробежаться, особо не запариваясь, здесь мы берем, собственно, различные импорты, понятно, описываем Define Next Plugin с помощью Define Next Plugin, берем Use State состояние, берем Query Client для того, чтобы его потом использовать.

Используем как раз-таки View App App Use.

Вы, кстати, таким же образом можете создавать любой плагин в Next.

То есть, допустим, если у вас есть какой-то библиотек, который только под View, вы создаете кастомный свой плагин, он прекрасно будет работать.

Если сервак, то мы делаем, допустим, hook app.render, и дегидрацию делаем, если клиент, то мы наоборот делаем гидратацию состояния нашего.

Гидратация – это когда мы получаем данные сервера и их подменяем на данные клиента, чтобы у нас такая плавность была определенная.

То, что было на сервере, что перешло в клиентскую часть.

Это называется гидратация данных.

Сохраняем.

В принципе, все.

Вроде бы нам даже не нужно его запускать нигде, если я ничего не путаю.

По-моему, даже он так уже будет работать в Nuxt.

Скорее всего, да.

Поэтому давайте мы продолжим описывать наш код замечательный.

Итак, сейчас стоит U-Square у нас.

Смотри, это, сразу говорю, это в U-Square пятая версия, то есть максимально свежий, новенький и так далее.

Он не так давно вышел, то есть для вас будет максимально свежий контент.

Но, по правде говоря, скажу, что они обновляются часто, и стоит иметь в виду, что, возможно, выход этого ролика уже у вас, допустим, шестая либо седьмая версия Они очень часто обновляются, но особых прям грандиозных изменений, я думаю, не стоит ожидать Просто они обычно косметические То есть, к примеру, раньше мы писали здесь вот таким образом, да, вот таким Теперь мы будем это писать немножко другим способом Теперь мы пишем это в объекте Пишем объект, query

Давайте мы сначала импортируем useQuery из tanStackViewQuery.

Теперь идем query function такой-то и query key такой-то.

Ключ у нас это массив.

Раньше это была строка, теперь это является массивом.

И указываем.

Здесь я укажу просто deals, то есть наши сделки, и запятая.

Если вы здесь указываете условно, получаете в useQuery что-то одно, то есть, к примеру, как мы будем получать одного клиента на странице, то через запятую указывайте его id, чтобы у вас, по этому ключу вы сможете в кэше отыскивать тот или иной элемент, скажем так.

И когда вы пишете id, у вас будет не дублироваться, и там будет deals1, deals2, deals3 и так далее.

Поэтому, когда вы делаете что-то одно, ищите, вам нужно вот так делать.

Query function.

Выполняем по классике стрелочную функцию и дальше.

Дальше берем db.

Вот, точка.

Нам нужно получить, есть у нас, давайте разберем подробненько, да, есть createDocument для создания чего-либо.

То есть db, это мы обращаемся к нашей базе данных appWrite.

Это для создания чего-либо, это для удаления чего-либо, это для получения конкретного одного элемента.

На самом деле, вот реально, если сравнить там Strapi, Supabase и вот appWrite, appWrite мне нравится больше всех.

Она суперкрутая, супертипизированная.

Как видишь, здесь все описано с помощью парамсов.

Мы, кстати, такую штуку делали тоже в интенсиве по JavaScript.

Прикольно это можно описывать, на самом деле очень удобно.

List documents, да, и библиотека, короче, очень крутая.

Вот, это мы получаем один документ, это получаем много документов, и это мы обновляем какой-то документ.

Нам нужно получить много документов, получаем, и вот вам подсказки.

Вам нужны Database ID, Collection ID и Queries.

Все максимально просто.

Database ID — это DB ID у нас.

Коллекция — это Deals.

Так, давай, Collection Deals.

И последнее, queries, вот, здесь, собственно, вот так получается, вот, здесь можно указать queries, queries, вот, как видите, наведем, да, вот, сейчас покажу, покажу, вот, queries, да, это именно какие-то доп.

запросы, доп.

параметры, к примеру, сортировка, допустим, фильтрация, это указывает все в queries, опять же, документация по этому поводу есть, документация очень крутая.

Вот, мы пока что оставим таким образом, да, тем самым получаем здесь то, что нужно получить, но нам этого мало.

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

Как это сделать?

Вот для этого есть еще вот объект «Опшенов» и есть специальненький «OnSuccess».

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

Производить определенные действия.

Сейчас такого нету.

Сейчас есть некая замена типа Select.

Ну, это немножко другая история.

Select предназначен для того, чтобы форматировать данные.

То есть, получили данные какие-то условно.

Это, опять же, одно из преимуществ ViewQuery.

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

Допустим, поменять какие-то данные в объекте и так далее.

Вот, кстати, для тех, кто вдруг будет спрашивать, а на что заменили onSuccess, почему его убрали и какая теперь рекомендация, теперь рекомендация следующая.

Вы берете таким образом, берете isSuccess, isSuccess, вот он, да, и теперь вы его либо в React UseEffect, либо в Vue с помощью Watch, вы его отслеживаете и при изменении этой истории что-то меняете.

Вот, это замена onSuccess.

Это для тех, кто будет спрашивать.

Но нам хватит select, берем select и внутри него будем производить очень много махинаций.

Для начала создадим новый массив.

Новый массив создадим на основе вот тех колонок, которые у нас уже есть.

Значит, мы берем new board, дальше kanban.data берем, вот то, что мы вот тут сделали, да, все наши колонки.

Дальше по ним мапимся, то есть по ним пробегаемся циклом.

Что значит map?

Map, в отличие от forEach, он возвращает нам новый объект, который мы описываем здесь.

На каждой итерации будет возвращаться вот такой объект.

То есть новый массив, в котором вот такие объекты лежат, вот такие объекты.

В каждом объекте у нас будет развернута колонка с помощью деструктуризации.

Каждая колонка, напомню, это вот эта вот.

Это одна колонка, это вторая колонка и так далее.

Понимаете, да?

И вот эти items, которые есть уже у колонки, вот они у колонки, они, скажем так, изначально, видишь, будут ругаться.

Мы должны примитивно задать, TypeScript будет ругаться.

Смотрите, если мы так сделаем, наведем сюда, мы увидим, что у нас items never.

То есть тип never, это тип по умолчанию.

Он нам не подходит 100%.

Поэтому в таком случае, а давайте мы не будем items.

Я что-то, видимо, перемудрил, когда писал этот код.

А зачем я items создаю?

Мы должны просто продублировать new board, новый массив взять.

Нам как бы и не надо здесь, знаешь... Слушай, я просто отдам kanban дату и все.

Вот.

Ну так я хочу новый массив создать, я вот так его разверну просто и отдам новый массив.

Все, это является новым массивом.

И мне это вполне будет достаточно, потому что зачем перемудривать, да, я не знаю.

Я ставлю просто так.

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

То есть просто клонирую, скажем так.

Вот, я не знаю, зачем я перемудрил, мне кажется, я лишний код написал просто.

Дальше мы должны сконвертировать, да, то есть мы конвертируем документы в сделки.

Что это имеет в виду?

Смотрите, у нас есть вот дата, да, определенная дата, вот она, приходит дата.

И как мы можем посмотреть, дата по умолчанию у нас является models.document.list, models.document.

То есть он не понимает, какой конкретно документ, какая конкретно коллекция.

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

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

Я взял только два поля, потому что только два поля мне нужны, но тем не менее у нас есть там пять полей условно, шесть.

Это понятно, но здесь, если мы не дадим ему конкретное понимание, что это за коллекция у нас непосредственно, то у нас будет ругаться TypeScript, потому что он пока что не видит полей самой сделки непосредственно.

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

А сделки у нас, напомню, это вот такая история, плюс вот эти поля.

Но напрямую указать не получится, будет ругаться, потому что у нас не сходится тип, он требует, чтобы это был документом и так далее Для этого мы указываем сначала, что это у нас неизвестность, а потом вот так, то есть в два этапа конвертируем и тип будет адекватно у нас отображаться Теперь посмотрим deals, deals наши deals, все, идеально работает

Причем, если вы посмотрите в консольке, оно реально является то, о чем я говорю.

Давайте даже посмотрим на этом этапе, я думаю, чтобы мы далеко с вами не ходили.

Возьмем deals и возьмем new board.

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

И вы видите здесь наш замечательный, ну пока я так сделаю, use kanban query.

Видишь, TypeScript не работает, здесь импорты, вот, хотя в React такое обычно всегда работает, в Vue.js в этом плане привыкайте, скажем так, да, вам придется руками все писать, это нормальный кейс для Vue.js.

Так, from, так, from откуда?

From is components,

Тут можно тильду, можно собачку.

И так, и так варианты поддерживаются.

Kanban и useKanbanQuery.

Дальше здесь делаем useKanbanQuery.

Я его просто пока вызову.

Без ничего лишнего.

Ничего забирать с него не буду.

Идем сюда.

Обновляемся.

Ошибка у нас.

Это нормальная ошибка.

Давайте перезагрузим просто наше приложение.

Ошибка должна пропасть.

Грузится.

И вот наша подгрузилась.

Смотрим консольку.

Нам важно посмотреть, документы пришли или нет.

Да, документы успешно пришли, как вы видите.

У нас первый массив абсолютно пустой.

А дальше все у нас успешно.

Странно, почему он пустой.

Вот этот момент меня очень сильно волнует.

Так.

Сейчас с тем будем разбираться.

А, вы за мной не видите, ребят?

Ну, короче, блин, надо вординги убрать, надо убрать вординги, чтобы они нам не мешали.

Сейчас мы их уберем тогда.

Короче, ну вот вы справите массив, да, который к нам приходит, это колонки, которые мы просто продублировали, а слева там написано array 0.

За моей вебкой там ничего такого нет.

Странно, почему 0 приходит на самом деле.

Потому что должны быть документы определенные.

Запрос при этом отправляется успешно.

Документ свод получаем.

А, у нас нет документов, ребят.

Почему странно?

Документов реально нет.

Давайте поднимем выше.

Опять же, мы увидим, что у нас вот документы, запрос ушел.

Cloud AppWrite Deals Documents.

Все успешно пошло.

200 статус ответа, значит, все успешно прошло.

И в ответе получаем .total 0, документ пустой массив.

Почему Total 0?

Потому что это идеальная концепция для погенации.

То есть, когда мы понимаем, какое у нас всего число элементов, мы можем там вывести погенацию нашу.

Это для будущего, если вам нужно будет реализовать, опять же, погенацию.

И пока документов нет, что логично, у нас ни одной сделки пока что нет.

Я думаю, что для теста можно сделку добавить, наверное.

Просто чтобы у нас была хотя бы одна сделка, чтобы понимали, что она вообще существует.

Давай перейдем в дату бейс.

Перейдем.

Ну, для сделки нам нужно будет кастомер.

Давай кастомера сначала сделаем, наверное, create document customer.

Да, у нас, допустим, будет какой-нибудь, не знаю, давайте ао альфа-банк.

Я не знаю.

Я решил так написать альфа-банк.

Почему-то вспомнился про него.

Так, e-mail какое-нибудь условие.

Давай альфа.

Альфа.

Что я сделал?

Я случайно, блин, я мискликнул, ребят, случайно нажал назад.

Так, вернем назад.

Так, ничего не создалось, да?

А как я так мискликнул, я не понял.

Еще раз давайте.

Альфа.

Банк.

Так, здесь, допустим, альфа.

Альфа.

Банк.ру.

Простой такой этот.

Аватар URL по умолчанию пусть будет from source.

Допустим, нашли мы у нас с Яндекс.Директа.

Яндекс.Директ.

И deal.

Пока что никакой сделки у нас нет, что логично.

Давайте next.

Create.

Все, создали.

Отлично.

Теперь идем в deal.

Создаем сделку с этим альфа-банком.

Create document.

Опять, чтобы у нас хоть что-то было сейчас.

Разработка сайта.

Разработка сайта.

Стоимость 500 тысяч.

Это вам не дешевый проект, все-таки с банком работаем.

To do по умолчанию, можно не ставить, на самом деле можно поставить, пока что в статусе только to do.

Комментариев пока не будет, понятное дело, и кастомер именно Альфа-банк.

Сохраняем, сохраняем.

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

Смотрим консольку, тоже у нас array перешел.

Опять же, за мной его не видно, но он прекрасно приходит, как видите.

Хотя, в принципе, должно быть вам видно.

В чем прикол, да, ребят, смотрите, отличие, вот вы недавно видели ролик на канале, если не смотрели, посмотрите, там был смонтированный наш стрим, тоже старались, надеюсь, вы поддержите тот ролик, хотя он получился не таким крутым, как этот ролик, но тем не менее, ваша поддержка будет важна.

Собственно, тот ролик мы делали про...

Социальную сеть.

И мы ее делали тоже на готовом бэкэнде на страпе.

Так вот, минус страпе в том, что мы с ним очень много промучились.

Вот в AppWrite они этот момент исправили и сделали в разы лучше.

AppWrite, как видите, он сразу разворачивает все поля.

То есть тем самым вам нужен кастомер.

Он его сразу полностью фулом развернул, и вы имеете к нему доступ.

Чтобы такое сделать в страпе, вам приходилось там постоянно просто бегать с бубном, делать неадекватные запросы.

И, короче, это темный мрак просто, вот.

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

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

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

У вас и так есть много чего во фронтэнде, еще и нужно бэкэнд учить, это очень сложно.

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

Вот, пожалуйста, уже второй ролик про эту историю.

Так что пишите в комментах, вам это нравится или нет.

Так, мы получили сделки, все в этом плане у нас отлично.

Далее, что дальше мы делаем?

Так, да, я хотел вординги убрать.

Смотрите, чтобы вординги убрать, создаем просто кучу страниц, которых у нас не существует.

Знаете, почему происходит?

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

Решение этого очень простое.

Создаете каждую страницу, и она у вас будет прекрасно работать.

Я, опять же, скопирую просто со своего другого проекта, чтобы просто, ну, опять же, сэкономить наше с вами время.

Так, у нас это что идет, получается?

Индекс, логин.

Так, индекс, логин мы не трогаем, все остальное мы трогаем, да?

Вот так.

Так, вроде бы все, скопировали.

Сюда Past.

Вот все ставили.

То есть здесь просто у нас идет обычный темплейт такой, ну просто пустой темплейт, просто чтобы они были заглушками, скажем так.

И не забудем еще одну заглушку сделать.

Так, Duplicate.

Не вижу Duplicate.

А, здесь нет Duplicate.

Это у нас будет Customers.

Customers.

Мы потом его сами с вами пересоздадим немножко в другой концепции.

Ну, пока так.

Теперь обновим все.

Как видите, ни одного warning нет.

Теперь у нас все отлично отображается.

Вот, чтобы вы могли консольку надо мной видеть сверху.

Отлично.

С этим понятно, я думаю.

Идем дальше.

У нас есть с вами сделки.

Замечательные, да?

Теперь задача распределить наши сделки по нашим колонкам.

То есть сделать правильную сортировку.

Чтобы не было мешанины, что все в одну колонку падает и все.

Нам нужно распределить конкретно по статусу.

У каждой сделки...

Так, секундочку, у каждой сделки, вот, берем одну сделку, у нее есть статус, вот, и этот статус должен мэчиться с каждой колонкой непосредственно.

Как мы это будем делать?

Для этого есть замечательный, просто крутой, невероятный и так далее, есть цикл, называется он ForEach, да, он нас ни на что не обязывает, на самом деле можно, знаете, что вместо ForEach использовать?

Можно использовать вообще ForOff.

Потому что он нам позволит также спокойно делать, но при этом с более читаемой синтаксисом.

Давайте воспользуемся форофом.

Вот я сейчас параллельно открылся, почитал в интернете, чтобы вам точную информацию донести.

На самом деле, я просто, форич для меня более привычен и удобен.

Ну, просто он реально более популярен.

Но на самом деле, когда вы используете форов, вы экономите, он быстрее на 33% примерно.

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

И допустим, когда мы делали интенсив по JavaScript, по чистому JavaScript, который на сайте есть, вот.

Там непосредственно мы с вами использовали for off, потому что он опять же лучше, и еще его плюс такой, что он более читаемый То есть for each это лишняя вложенность определенная, да, что тяжело читается, а for off более такой читаемый в этом плане Вот, поэтому такая статистика, давайте попробуем for off заюзать, и опять же для вашего разнообразия пишем for Дальше мы указываем const, как будет называться у нас deal, off, deals

И разворачиваем.

Все.

Вот вместо вот такой кашицы, если бы мы здесь делали, допустим, вот так, да, deal, вот так, вот так.

То есть вместо вот такой кашицы мы делаем вот такой полноценный for off.

Мне кажется, сочетаемость гораздо лучше.

Ну, опять же, на вкус и цвет как бы, да.

Теперь мы ищем колонку.

Нужно нам колонку, да, по нашему deal.

То есть мы берем колу.

И в нашем New Board, в этом, да, ищем нашу нужную колонку.

Как мы ее ищем?

Мы берем новую колонку, вот эту, которую мы только что создали.

Не колонку, новый наш борт, вот этот весь борт.

Ищем по нему нужную нам колонку.

Берем Column.

Давайте я, чтобы более читаемость лучше была, я напишу Column.

Ну, хотя это будет конфликт.

Ну, конфликта не будет, но не хочу, чтобы вы и здесь Column, и там Column.

Пусть будет Column.

Column.

Берем Column ID.

То есть это наш Enum, помните, да, вот эти Column ID.

Вот Enum, вот эти Enumчики наши.

Если оно равно статусу, который тоже является Enum по факту, у самой сделки, то значит это текущая колонка непосредственно.

Вот.

Еще раз, да?

Получаем данные сервака, получаем нужную нам колонку и в нее записываем нужные нам данные.

Вот и все.

Если колонка будет найдена, вот таким образом мы делаем columns, items push и в нее пушим все наши значения.

Я специально, смотрите, мы могли бы сюда целиком закинуть прям сделку всю, просто вот так сделать, да, условно, deal.

Но там много мусора.

Помните, там много лишних полей ненужных.

Я не хочу так делать.

Я хочу закинуть только то, что мне надо.

Поэтому я вот таким образом все устраиваю.

Забираю deal и конкретно каждое поле описываю.

Пусть оно занимает больше срок кода, но более читаемо, на мой взгляд.

Здесь customer в единственном числе.

Дату забираем, ID забираем, name забираем, price забираем, company name забираем и статус тоже забираем.

Все.

Обратите внимание, что company name это из кастомера берем name.

Это называется company name.

Опять же, вы можете переменять на свой вкус и цвет, как бы это вас никто в этом не ограничивает.

И в конце концов, да, мы просто с вами берем, после пуша мы возвращаем уже готовый наш замечательный new board.

Вот.

Провели с ним определенные махинации, определенные действия, и реально читаемость стала в разы лучше.

Ну, то есть я рад, что я именно вот на записи использовал forof, потому что с ним читаемость лучше.

Вот реально.

Ну, прям в разы лучше.

Так что вот такая история.

Все.

Квери у нас готов полностью.

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

Смотрите.

Значит, берем useQuery, назначаем ключ для того, чтобы по этому ключу мы могли в кэше в будущем его получить, если нам это потребуется.

Ну и вообще во избежание конфликтов ключ должен быть всегда уникальным.

Дальше берем функцию query, по которой получаем все наши документы.

db.listDocuments, указываем ID-шник нашей базы данных и указываем ID-шник нашей коллекции.

Получаем полностью все наши айтемы.

Дальше с помощью select после получения производим с ними определенную трансформацию.

То есть this option может быть использован для трансформации или для select определенной части наших дата, которая возвращается из этой функции.

Получаем нашу дату.

Сразу обозначаем, что дата у нас является по факту сделками нашими.

Дублируем наш Kanban дату, которую мы изначально сделали, все наши колонки, мы их дублируем.

И в них просто с помощью цикла forof ищем нужную колонку и в нее пушим нужные нам айтемы.

Все.

Ищем колонку, запушили айтемы, готово.

В конце возвращаем наш готовый уже весь борт, чтобы потом можно было его использовать.

Вот такой компактный, крутой получился реально query-запрос.

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

Мне кажется, это прям хороший результат.

Сохраняем.

Так, с этим все закончили.

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

Здесь получаем дату, здесь получаем излодинг и здесь получаем рефетч.

Дата из loading и refetch, вот, смотрите, дата нам нужна для того, чтобы получить вот ту самую дату, которую мы уже с вами описали, вот, дата это у нас является чем, подсказок я не вижу пока, дата value напишу, а вот дата value у нас является, вот, да, я уже думал, что опять нет ничего, но на самом деле есть, да, является колонками, что в корне верно

Из loading состояние загрузки, понятно, да?

А refetch нужен для того, как я и говорил, чтобы мы при перетаскивании, когда выполним запросы сервак, мы при успешном запросе переобновили всю нашу историю.

Для переобновления служит refetch, то есть он заново отправит запрос и переобновит наши данные.

В этом вот и плюс в Uquery.

Я предлагаю пойти это все дело вывести, да, потом займемся уже drag-and-drop непосредственно переносом, да, пока что давайте выведем всю эту историю.

Во-первых, в этой части я поставлю такую простую штучку.

Обычный div с надписью «loading» и написано «если загрузка, то будет loading».

Почему я решил сделать так, а не, допустим, взять и использовать наш вот этот большой лодер, который у нас был?

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

Мне кажется, это будет излишне.

Но если вдруг вы захотите использовать наш глобальный лодер именно, берете, вешаете watch на изloading, да, и в callback дальше меняете наш глобальный state.

Вот, я не буду все писать, чтобы у вас было небольшое место на подумать.

Все, есть загрузка, мы пишем loading просто, а иначе мы делаем вот этот наш div, div else.

Отлично, теперь можно написать наши карточки, но перед карточками нужно описать наши колонки непосредственно.

Итак, для начала мы сделаем грид, да, вот для наших колонок, давайте я чуть-чуть их отближу, то есть у нас идет сетка, 5 колонок, вот 5 колонок и между ними расстояние 16.

Далее я сделаю еще div для непосредственно каждой колонки.

Мы берем нашу дату, которую мы с вами сгенерировали, трансформировали.

И с помощью v4 мы с вами уже разбирали эту директиву.

Мы пробегаемся по каждой итерации, по этому массиву.

И на каждой итерации получаем индекс и колонку саму, чтобы вывести данные.

Индекс нужен для краски градиента, чтобы мы обозначили градиент.

Дальше мы внутри этого дива, да, делаем еще непосредственно вот эту стилистику.

Это будет стилистика непосредственно для каждого вот этого айтема.

Ой, ребят, я заговорился немножко.

Не для каждого айтема, а непосредственно для колонки, для каждого названия колонки.

Мы сейчас описываем непосредственно заголовок, бейдж-заголовка.

Column name, div, вот тут bg-slate.

По умолчанию, да.

И div оборачиваем дополнительным еще нашу карточку.

Сохранимся.

Здесь не забываем ключи обозначить.

Это ключ, да, column id.

Так, что-то мы забыли какой-то div лишний добавить.

Так, div здесь есть, здесь есть.

Где-то мы забыли div закрыть.

Вот здесь мы должны его закрыть, по-моему.

И еще один, да, div забыли закрыть.

Вот так.

Все, div все позакрывали.

Я, на всякий случай, сделаю перезагрузку, чтобы у нас точно все заработало.

И давайте посмотрим, что получается в итоге.

Так, грузится, грузится, грузится.

И вот наши айтемы появились.

Отлично.

Все прям шикарненько у нас.

Как видишь, данные все успешно записались.

Ну, у нас пока что нейм-карт компании идет.

Да, данные почему-то не подгрузились.

Так, а, ну потому что мы их даже не заменили.

То есть мы пока что просто вот дублируем с вами одну и ту же карточку на каждой итерации.

Что в корне неверно, поэтому давайте ее также продублируем.

Смотрите, значит, нам нужно делать цикл внутри цикла.

То есть у нас есть цикл, первый цикл, да?

Но внутри него есть другой цикл, именно карточек.

То есть, смотрите, у нас много колонок, и в каждой колонке много карточек.

Это значит, нужно два цикла.

Первый цикл мы сделали, а второй цикл мы делаем в этой части.

Берем UI-карт, v4-карт из колумб, а в нем, помните, есть айтемы, которые мы назначали, и на каждый раз берем карт.

Дальше обозначаем ключ обязательно, это будет kart.id.

Ну и давайте уже заполним нашу карточку непосредственно.

Значит, первая name-карт будет непосредственно у нас kart-name, просто берем kart-name.

Дальше я хочу обозначить цену, вот, но я хотел бы вот эту цену, вот эта цена, видишь, она в специальном формате И она в таком формате, в формате цены, собственно, да, логичной цены с валютой и так далее Давайте ее также обозначим, я вставлю, собственно, uicar description, который мы создавали уже, да И сделаю функцию convert currency, давайте ее, собственно, обозначим в наших утилитах Перейдем в утилиты, new file, convert currency.ts

Функция будет максимально простой.

Мы берем функцию стрелочную, создаем, дальше принимаем в нее amount, то есть какое-то значение, какое-то число, либо строку нам не важно абсолютно, и возвращаем это дефолтный джаваскриптовский, скажем так, утилита по изменению из namespace по локализации.

Объясню.

New.

Потом ставим такие инициалы.

Я не помню, как они переводятся.

Чисто я раньше думал International.

Но это как-то по-другому переводится.

Так, давай посмотрим.

Так, сюда посмотрим.

Так.

Так.

Или я все правильно думал?

Internalization, да, это переводится?

Да, я, по-моему, правильно все думал.

Internalization переводится.

Она вам позволяет и формат менять для цифр, и для даты, и что угодно.

Позволяет вам менять максимально комфортно.

И опять же, могли, кстати, даты менять тоже с помощью нее.

Но я просто Date.js больше люблю, поэтому я через Date.js все это делал.

Значит, мы берем international.number формат, потому что работаем с числом именно.

Указываем ваш регион или регион, под который вы хотите сделать формат числа.

Он в каждой стране разный.

К примеру, в Америке они ставят запятую, допустим.

Ну, какие-то разные еще концепции.

В Европе что-то еще про белый, возможно.

Короче, в разных странах по-разному.

Я указываю российский регион.

Дальше style указываем currency.

И саму валюту указываем рубли.

Вот.

Чтобы у нас в конце добавился именно валюта.

И в конце форматируем.

Точка формат.

Ставлю плюсик, чтобы сразу поменять в число, если вдруг оно пришло в формате строки.

Все.

Вот такая функция по конвертации в валюту.

Мы импортируем.

Обязательно, опять же, импорты не работают.

Не забываем про это, да.

Нужно идти руками все писать.

Импорт.

Convert to currency.

From.

Значит, сюда.

Utils.

И вот Convert to currency.

Отлично.

С этим, я думаю, закончили.

Так, дальше идем.

Сама компания.

Компания у меня будет также компания, но добавляем еще карт company name.

И давай теперь воспользуемся вместо даты day.js.

Для этого ее импортируем.

Я его, по-моему, уже поставил, если я ничего не путаю.

Import day.js from day.js.

Идем сюда.

И вставляем.

Смотрите, Date.js работает максимально просто.

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

Если прокидываем, вы можете прокинуть формат даты прямо с базы данных, прямо целиком весь формат.

Вот этот timestamp, который любят все базы данных.

Прокидываем сюда.

Дальше через точка «Формат» вызываем метод формата, в котором мы указываем формат, который нам нужен.

Я хочу вот такой формат получить.

То есть сначала идет «День».

Пишу «День 2D», потому что короткий формат «Дня».

Длинный формат месяца, поэтому 4M.

И длинный формат года, поэтому 4Y.

Вот.

Соответственно, если хотите более короткий формат, пишите там в два раза меньше.

И все, сохраняем, собственно.

Давайте посмотрим, что получилось.

Обновляемся.

Как видим, вот штука все у нас отобразилась.

Разработка сайта 500 тысяч рублей.

Компания АО Альфа Банк и вот такая у нас дата получилась.

То есть то, что нам нужно на самом деле.

Опустимся сюда и посмотрим, чего нам не хватает.

У нас что-то с цифрами происходит, они куда-то скачут, что-то им явно не хватает.

Я думаю, что мы кое-что забыли указать в дескрипшене.

Сейчас я посмотрю, попробую это указать.

Или даже знаете, где мы забыли указать?

По-моему, в карт-контенте.

Карт-контент, да.

Уйдем сюда.

У нас что здесь идет?

Смотрите, у нас идет P6.

Давайте паддинги, во-первых, уменьшим.

У нас паддинги меньше идут.

У нас P3 должно быть.

Вот видишь, у нас паддинги поменьше идут.

Но это не все, да?

Что-то не хватает здесь.

Что-то не хватает стилистики определенной.

Сейчас будем чуть-чуть подменивать.

Так, давайте посмотрим саму карточку.

Ну, карточку мы меняли уже.

Карточку мы точно меняли.

Здесь особо-то и нечего.

Мы здесь все подготовили с вами.

Окей, пойдем в Description.

Здесь тоже все в порядке.

Пойдем в Footer.

footer тоже, а нет, footer нет, footer не все в порядке, footer подменю немножечко, вот такие стили я решил использовать, padding-3, text-xs, opacity-70 и talic, вот, так, для хедера тоже другие немножко стили, для хедера вставляем сюда, text-xl, font-semi-bold, leading-0, отлично, и car-title,

А, прошу прощения, это было для тайтла непосредственно, да, это было для тайтла.

Вставляем это для тайтла, а для хедера тогда другая история.

А для хедера все то же самое, но чуть-чуть опять же меняется для хедера.

Все, вставляем.

Сохраняем, закрываем всю эту историю, перезагружаем наш замечательный Next.

Ждем перезагрузки полной, очистки кэша.

Танцтак можно закрыть, кстати говоря.

Что-то он подвис, мне кажется.

Давай перезагрузим полностью.

Вот, все, заработало.

Так, вроде бы стало лучше, но мне опять не нравится.

Я не понимаю, почему у нас не подгрузилось.

Странно.

Странная история.

Ну и ладно.

При этом у нас есть... У карты должен быть паддинг.

Паддинг 3.

Подожди, где наш кард?

У карта что, нет паддинга, что ли?

Странная история.

Короче, я не знаю, как так получилось, ребят, но, походу, в дескрипшн нужно паддинг добавить.

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

Ну, ладно, добавим, уже не суть.

Просто у нас по макету немножко не соответствует, как видите.

И причем здесь паддинг 3 идет, space y 1.5, да?

Немножко не соответствует нашему макету.

Давай чуть-чуть поработаем с этим.

Мне не нравится, как оно смотрится все.

Давай... Значит, смотри, сейчас нужно разобраться, что и где.

Значит, card title.

Я делаю текст Excel.

Пусть будет... Подожди, а где мой Excel?

Стой, стой, стой.

Смотрим сюда.

Может, я перепутал что-то?

Подожди, может, я перепутал местами?

Смотри, card header.

Да, я, по-моему, перепутал и не дописали мы немножечко.

То есть у нас есть card header, а в нем уже есть card title непосредственно.

То есть есть card header...

Да-да-да, и он распространяется также и на description тоже Вот, стоит не забывать, вот так card title и на description тоже распространяется, вот таким образом Вот, вот так более правильно, то есть card header, в нем идет card title, card description, и там уже пошло-поехало Вот, так уж другое дело Но опять же, вот это вот касаемо card content, я здесь делаю класс, мне он не нравится, я ему сделаю текст sm

Мало.

Давайте XS поставим ему.

Вот, компания Ольфа Банк.

Я так оставлю.

Мне кажется, просто так будет гораздо лучше.

Вот, и теперь смотрится все более-менее адекватно, мне так кажется.

Наверное, да.

Может, перебор пожирности?

Ну, это уже такая, знаете, мелочь.

Там уже можете у себя доработать, если вам это актуально будет.

Так, здесь чуть тоже себе добавлю еще класс MarginTop.

Там один, допустим, вот, чтобы чуть-чуть...

Подрихтовать, скажем так Но это является у нас паддингом, поэтому работать не будет То есть лучше тогда идти от Title на самом деле Title у нас чем является?

H3 Тогда знаешь, как сделаем?

Блоку добавим просто, блок Ну типа такого, даже я бы двоечку поставил, наверное Так, обновимся еще раз

Пойдет.

Все, очистим кэш.

Мы там что-то, по-моему, меняли.

Должно все работать.

А, ну опять сейчас будет, блин.

Сейчас нужно убрать, ребят, из Description.

Уберите теперь Padding 3.

Он теперь здесь не нужен, потому что мы обернули немножко иначе все.

Так, вот так.

Все, сохраняем.

Обновляем еще раз.

Все, теперь точно все будет работать.

Так, с этим понятно, да?

Ну что, делаем функционал переноса, получается, тогда?

Или добавление карточки новой?

Давайте подумаем, как лучше нам сделать.

И то, и то интересно.

Давайте по очереди, наверное, да?

Сделаем сначала, наверное, добавление карточки новой, а потом сделаем перенос.

Я думаю, так мы поступим.

Все, все грузится отлично, все красивенько у нас.

Кайф.

Как видите, перетаскивается, но эффекта никого не производится, что логично.

Давайте сделаем функцию добавления.

Видите, у нас такой плюсик есть необычный.

Его здесь, может, вам на видео даже не видно.

Я его сделал таким малопрозрачным, но при этом он очень приятный с анимацией.

Окей, переходим, ребята, к созданию сделки.

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

То есть, помните, да, на страничке логина мы реализовали эту всю историю через рефы обычные, классические, да.

Теперь покажу вариант реализации через такую правильную валидацию, да, через более такую продвинутую, как у нас есть в React, React Hook Form.

Здесь есть также VValidate.

Но VValidate мне не так сильно понравилось, сразу скажу вам, потому что она как будто очень сырая и недоработанная.

Вот, честно, такое впечатление создалось.

Но, тем не менее, во Vue другого варианта нет, поэтому давайте ее используем.

Так.

Вот если бы сделали библиотеку viewHookForm, мне кажется, это было бы прям конфетка, но, видимо, не судьба.

Для этого перейдем в Kanban, вот наш Kanban, и создадим новый файл.

Это будет наш компонент createDeal.view.

Компонент будет отвечать только за то, чтобы создавать нашу формочку, да, и создавать нашу сделку непосредственно, вот.

Итак, по классике возьмем нужные нам импорты.

Импорт useMutation берем.

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

А касаемо useQuery, который мы обычно использовали, он именно на получение, а useMutation на обновление чего-либо.

Потом берем UID, как обычно.

Дальше берем Define Props.

Для чего?

Define Props нужно для того, чтобы мы описали входящие данные.

Опять же, во Vue, хочу пожаловаться, это сделано не так идеально.

То есть в том же React это гораздо лучше реализовано.

Вот эти эпизации данных, которые входят и так далее.

Здесь как-то, ну, честно признать, через одно место, потому что, ну, я вам покажу, как это сделано.

Это вообще, ну, мне не понравилось, короче.

Дальше берем Collection Deals и DBID для того, чтобы к ним сделать запрос непосредственно.

И берем IDL, нам тип также потребуется.

Вот.

Ну, пока же давайте опишем визуал.

Темплейт.

Отлично.

Итак, базово, смотрите, у нас кнопочка вот такая.

Она располагается по центру.

И она еле-еле видима.

Вот она, кнопочка наша.

Ну, вот так, я думаю, вы ее должны увидеть.

Серенькая такая.

Она должна еле быть видна.

При нажатии на нее она открывается, красивенькая менюшечка.

Смотрите.

Текст center margin bottom 2 обязательно, да.

Дальше делаем саму кнопку.

Сама кнопка делается с помощью баттона.

Вот наш баттон замечательный.

Добавляем в следующий класс transition all, потому что будет transition не только цвета, но еще и opacity, поэтому делаем all.

По-моему, сейчас у меня opacity 5, это очень мало, 5%.

Дальше мы делаем клик.

Событие клика, оно пока не работает, потому что у нас нет как такого открытия пока что, да.

Но это будет наше состояние, которое мы будем менять.

Состояние чего?

Открытие либо закрытие формы.

То есть мы, открытив состояние, пишем равно и обратно даем состояние.

То есть каждый раз оно будет либо true, либо false.

Что это значит?

Это эффект toggle.

То есть если у нас сейчас открыта форма, она будет закрыта.

Если закрыта, будет открыта.

Потому что мы каждый раз делаем обратно от текущее состояние.

Дальше берем первую иконку, эта иконка будет именно появляться, когда форма открыта, when if open form, то есть когда формочка открыта, иконка плюсика меняется на стрелку вверх.

И вторую, непосредственно вторую иконку, это плюсик, это то, что мы сейчас видим, то есть плюсик будет в момент, когда у нас форма закрыта, это будет когда форма открыта.

Давайте создадим теперь состояние из open form.

Это мы с вами уже делать умеем.

Просто const из open form, ref boolean.

Опять же, boolean можно не писать, можно писать, я так написал.

Вы, когда по умолчанию пишете значение здесь false, оно уже будет boolean по-любому.

То есть можно это не писать, как в React, допустим.

Так что здесь уже на ваш вкус и цвет, опять же.

Так, дальше.

Вот у нас есть див замечательный, да?

Теперь есть кнопочки.

Так, здесь делаем форму.

Вот тут форма будет.

Но форму пока описывать не будем.

Она очень проста, на самом деле.

Мы уже с вами описывали форму в логин-форум, поэтому особо ничего новенького мы не увидим.

Но опишем чуть попозже, когда напишем сам функционал, потому что нам проще будет сразу закрепить ее со стейтом, и так будет гораздо проще.

Поэтому опишем стили.

Стиль у нас базовый.

Опять же, их вставлю просто, чуть опишу.

Это input.

Поле для ввода, да, вот таким образом я его описываю.

Это будет button, кнопочка.

Это будет форм, формочка, да, и она будет красивенько открываться, плавненько, с таким эффектом.

Вот, эффект, кстати, я взял у карточки, только чуть его подизменил.

Вот, тоже имейте это в виду.

Опять же, напомню, проект можно скачать бесплатно по ссылке в описании.

Итак, с чего начнем описывать нашу форму?

Как и в React, опять же, для описания формы через библиотечку, вот мы будем использовать сейчас VValidate, нам первое, что нужно сделать, это описать тип нашей формы.

То есть каждое поле описать, какой он будет тип и так далее.

Для этого создадим интерфейс.

Интерфейс, назовем его IdealFormState.

Экстендить мы его будем от Ideal, от нашей сделки, да, но с помощью пик.

utility types, по-моему, это называется, мы берем конкретные поля из IDL.

Вот наш IDL, но нам все поля здесь не нужны.

Возьмем только конкретные поля.

И я беру именно через пик.

В последнее время я его полюбил больше, чем OMID.

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

А пик позволяет взять наоборот поля, которые мы указываем.

Мне нравится больше пик, потому что пик наглядно, визуально показывает, что мы берем и в итоге, что мы получаем.

Поэтому я использую пик.

Берем IDL.

И нам нужно здесь получить name и price.

Все, нам только два поля.

Все остальные поля мы допишем руками, потому что оттуда они нам не подойдут.

А руками мы дописываем следующие поля.

Это customer, у него будет email string и name string.

И это статус.

Статус будет стринга по умолчанию.

Все.

Это вот такие поля обязательно.

Причем статус мы в нашей формочке, мы статус не будем задавать сами, да, писать.

Статус будет исходя из нашей колонки, что логично.

То есть в какой колонке мы нажали, от той колонки будет статус переходить.

Теперь опишем как раз-таки входящие пропсы.

То есть вот этот компонент будем использовать внутри предыдущего компонента вот этого индекса непосредственно.

И оттуда мы будем перенаправлять, допустим, какая сейчас у нас колонка открыта и так далее.

Поэтому берем здесь const props, пишем define props, define props.

Делаем объект, пишем статус и описываем.

Вот так все немножко тупенько описывается.

Type string.

Причем это string, это не TypeScript string.

Это string, просто string конструктор.

JavaScript обычный.

И это, конечно, ужасно.

И по умолчанию будет пустое значение.

Это, конечно, мне не нравится.

То есть мы в React, допустим, типизируем обычно TypeScript.

Здесь мы типизируем какую-то шляпу, честно признаюсь.

То есть да, мы видим, что props это статус string.

Ну, допустим, если я здесь хочу какую-то функцию указать, мне приходится писать function, допустим, да Если хочу указать там что-то другое, мне приходится как-то извращаться Почему нельзя было добавить обычный TypeScript, я не понимаю Вот, и refetch, как раз-таки, да, для того, чтобы мы переобновляли То есть, когда добавилась новая карточка, переобновился кэш, получили новый наш Kanban весь А refetch будет по Type, как раз-таки, function, про что я только что вам и говорил То есть, обычно в TypeScript это будет вот такая штука, void

Вот, да, здесь это является function Вот такие вот, сохраняем Вот наши пропсы, которые приходят извне И опять же, в чем минус в U и интеграция стоит с криптом Вы никогда в жизни не увидите ошибку о том, что вы забыли прокинуть пропс Если мы сейчас этот компонент возьмем, да, и воспользуемся им вот на этой части Он у нас где там будет?

Вот, перед UICard пишем, значит, kanban createDeal

Вызываем его, да, и смотрите, он нам даже, видишь, он говорит, что здесь есть эти статусы, вот, при наведении Но из-за того, что это не TypeScript, он не говорит конкретно, что нам нужно это обязательно прокинуть То есть вроде бы ты можешь прокинуть, а можешь и не прокидывать То есть нет никакой валидации Хотя на самом деле поля являются required у нас Давай проверим, может я что-то не прав Тут даже нет required, да?

А, есть required Давай, окей, я может быть, ребят, сейчас заберу свои слова обратно, может я не прав

Вот, пишу required, смотри.

И что мы видим?

Required true, но при этом здесь он опять же не ругается.

Хотя в нормальном TypeScript приложении он должен ругаться на то, что мы забыли прокинуть пропсы.

Короче, вот такая шляпа получается.

Мне это не нравится.

Это все происходит потому, что Vue изначально не был создан под TypeScript, поэтому стоит это понимать.

Дальше делаем саму формочку, но перед этим давайте ее сначала установим в проект.

Напомню, библиотечка называется vvalidate.

Вот такая библиотека.

Так, get started.

Значит, опускаемся ниже.

Вот, yard dv validate.

Я не знаю, для Nuxt там есть отдельная установка или нет.

Я, честно, уже не помню.

Nuxt integration, да, поставим.

По-моему, есть, да, отдельно.

Да, вот, отдельно dv validate для Nuxt именно.

Копируем и идем сюда.

Вставляем, устанавливаем.

Дальше мы экспортируем сам модуль vvalidate, переходим в next.config и вот сам модуль сюда указываем.

Таким образом.

Сохраняем.

Дальше.

Опускаемся ниже.

Как видите, он предлагает вам сделать сразу автоимпорт определенный.

Это описывается вот здесь.

Значит, мы описываем vvalidate, disable auto imports, нам нужны auto imports, и component names.

Слушай, ну я auto imports добавлю точно, а вот эту историю не хочу добавлять.

Мне кажется, это бесполезно.

Так, вставим сюда тогда вот таким образом и закроем скобочки.

Я хочу auto imports, чтобы у меня были, но ни в коем случае, чтобы у меня не было выполнено чего-то из этих штук.

Ну, мне не нравится, по крайней мере.

Далее Так, это что?

Это мы все описали уже Вроде бы все получается, да, в таком случае?

Наверное, да Ну и здесь сама документация есть, о которой мы сейчас поговорим непосредственно Так, возвращаемся к нашему проектику Закрываем Опять же, обновлю кэш Не знаю, может быть, поможет как-то Ну, чтобы просто у нас были свежие данные Здесь описываем const UseForm UseForm берем из VValidate

И указываем определенные options, если они у вас есть.

У нас будет один option, единственный это initial values, то есть значение по умолчанию.

Помните, я вам говорил, что мы хотим статус брать именно с колонки, а не сами вводить его.

Поэтому этот статус, который к нам приходит из props, мы его обозначаем здесь.

Видишь, у нас ничего не подсказывает, потому что забыли generic, прокинуться от generic.

IdealFormState.

Теперь должны подсказки работать.

Да, вот, пожалуй, статус.

Статус будет являться двоеточие props.status.

Вот таким образом.

В ответе нам нужно получить handle.submit.

Это то, что будет вызываться непосредственно самой формой, да, при ее сабмите непосредственно.

Ну, при нажатии на кнопку условно.

Define field для описания полей и для связи их с инпутами.

Опять же, здесь это сделано через одно место.

В том же самом, опять же, hook.form сделано гораздо удобнее.

Я вам об этом чуть дальше поговорим.

И handle.reset.

Для очистки нашей формы, для сброса ее Дальше делаем const DefineField DefineField Здесь делаем путь Я задумался, ребят, заговорился Не путь мы здесь делаем, да А вот как раз таки описываем нашу форму Смотрите, нам сейчас что важно сделать У нас есть DefineField Для того, чтобы связать наши инпуты с нашей формы Чтобы у нас, когда мы, помните, мы с вами Когда на страничке логина сравнивали Точнее связывали наши поля Со стейтом, да

Нам нужно получать текущее значение.

Соответственно, раньше мы это делали через ref, условно.

Теперь нам это нужно делать через defineField.

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

В нашем случае это к полю... Так, у нас это к полю получается name.

Да, начнем с name, наверное.

Вот, и он нам отдает такой кортеж определенный.

Я это называю кортежем, многие меня не любят за это и вообще не любят, что я там называю слово кортеж в JavaScript.

Смотрите, что я считаю, ребят, кортежем?

Кортежем я считаю тогда, когда у нас отдается конкретный массив данных, но при этом, когда мы знаем его четкое количество данных, да, и знаем четкие элементы.

То есть вот это является кортежем.

А если бы мы сделали, допустим, ну не знаю, там, допустим, const data...

Вот это, да, это называется уже массив, потому что здесь идет, ну, большое количество данных.

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

Дальше мы дублируем.

Здесь у нас будет name, дальше у нас будет непосредственно price.

Price.

Дальше у нас будет непосредственно этот email, да, по-моему, у нас идет, не, не email, а как там, customer email, да, и customer name еще.

Customer name.

Вот.

Все, с этим хорошо.

Так, здесь только меняем на customer.

Так, давай я вот так выделю совместно.

Попробую выделить все.

Не получилось.

Ладно, фиг с ним.

Customer.email.

Customer name и customer email.

Вот так.

Все.

Это наши поля описаны.

Опять же, напишите в комментах, может быть, есть какой-то способ.

Опять же, в документации я не нашел.

Как-то это компактнее сделать.

Просто в React мы просто берем условно пишем регистр, берем поле какое-то и его регистрируем.

Все.

И это уже сразу к input вяжется автоматически.

Нам не нужно состоять, вот это дублировать.

Зачем лишние эти строгие коды, я не понимаю.

Может быть я что-то не дочитался, я не знаю, может быть такое вполне.

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

Сейчас, дай-ка подумать, как лучше сделать.

Давайте сейчас все-таки мутацию опишем, мне так будет удобнее, да, потом перенести сразу целиком все, поэтому опишу мутацию.

Так, use mutation.

Смотрите, то есть эти поля мы свяжем, да, но представим, как будто мы уже связали, здесь есть уже конкретные данные, конкретные данные, которые можно использовать.

Сделаем use mutation.

Use mutation.

Так, mutation.

Опять же, нет подсказок.

Почему-то нет их вообще абсолютно никаких, хотя должны быть.

Давай поднимемся выше и их используем, да, непосредственно.

Так, import.

Так, from...

From view.

Так, давай, сейчас.

TanStack.

Так, ребята, я что-то не вижу.

Мы же только что использовали TanStack.

Так, давайте наш замечательный view еще раз попробуем посмотреть.

Вот view query.

Странно, почему не подсказывают мне.

Так, use mutation отсюда.

Все.

Странная история.

Так, дальше идем у нас.

Mutation function.

Сначала ключ указываем.

Ключ будет у нас непосредственно создание сделки.

То есть create a new deal.

Значит, сама функция будет следующего характера.

Значит, как происходит функция на добавление?

Принимаем сюда определенную дату.

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

Мы ее разве не описали?

Давайте посмотрим.

А, ну мы по факту... Смотрите, дата — это как раз-таки наша дата.

Тут особо-то ходить некуда.

Это наши поля все.

И дальше мы их добавляем.

Делаем db, createDocument.

Здесь указываем dbId.

Дальше мы указываем collection ID, то есть это у нас идет deals, collection, collection deals.

Отлично.

Берем ID-шничек.

Так.

И берем дату.

Дату отсюда мы забираем непосредственно.

Вот наш ID-шничек здесь.

Так, ID-шничек забыли взять.

Копируем, закрываем.

Вставляем.

Вот наш ID-шничек, замечательно.

Так.

Это мы сюда вставляем.

Окей.

Дальше здесь при success, on success.

Берем дату.

И если у нас приходит props.refetch, то мы тогда делаем props.refetch.

Я специально так пишу, потому что TypeScript иначе будет ругаться.

То есть при создании сделки мы должны переобновить другие сделки.

Вот что важно.

Чтобы у нас обновился весь наш Kanban.

Поэтому мы это и делаем.

Так, дальше.

А дальше делаем HandleReset, сбрасываем все данные.

HandleReset.

Все.

То есть полностью очищаем нашу форму.

Вот наш HandleReset выполняем, полностью очищаем форму.

Это верное решение, поэтому так мы и делаем.

Тут мы забираем мутацию.

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

Смотрите, в чем отличается Mutate от Mutate Async.

Многие, опять же, будут спрашивать, и не все смотрели мой курс по React Query.

Вы его можете посмотреть, кстати, на канале, потому что в UQuery и React Query очень сильно похожи.

Смотрите, Mutate вам позволяет обрабатывать ошибки, сексессы, лодинги на уровне вот этой мутации.

То есть пишите здесь «onSuccess», «onLoading», «onPadding» и так далее.

А mutate async позволяет вам это все вынести, обработку, уже к себе.

То есть чтобы вы сами ее обрабатывали.

Через try-catch, к примеру.

Вот.

Это и отличается.

В основном все используют mutate, потому что тут гораздо выгоднее и удобнее все это дело использовать.

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

const onSubmit.

Оборачиваем handle.submit из вот этой штуки, из формы.

И берем values и mutate values.

Все.

Это классика.

Теперь опишем нашу формочку непосредственно.

Во-первых, форма будет появляться только тогда, когда она открыта.

Соответственно, мы пишем v if isOpenForum.

Дальше, как и любое событие во view, мы уже с вами это разбирали, мы делаем через собачка.

Собачка submit или собачка click.

Ну, вы сами поняли, уже опять же разбирали.

И класс form, который опять же ниже у нас уже инициализирован.

Берем формочку, закрываем, открываем ее и вносим поля.

Касаемо полей, смотрите, UIInput берем.

Я это решил не объяснять, ребята, потому что мы это уже брали в логине, мы уже с полями умеем работать, и я не хочу дублировать, чтобы у нас ролик и так долгий получается, поэтому хочу сэкономить максимально время.

Значит, берем UI input, дальше берем placeholder наименование, vmodel name, vbind name address.

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

И таким образом работает связь, то есть мы связываем ее с формой.

То же самое для всех полей, для e-mail, для компании, для суммы, все так же работает абсолютно.

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

Все, сохраняемся.

Теперь здесь давайте укажем то, что она от себя требует.

Она требует у нас указать refetch, который у нас есть, поэтому указан просто refetch.

И у нас также есть, вот вишни подсказывают, я не помню, статус есть, да, статус.

А статусом у нас является column.id, это наш статус.

Предлагаю проверить, что у нас получилось вообще.

Обновляемся.

Так, смотрите, у нас я вижу слабенькая кнопочка, очень слабенькая кнопочка, она прям еле видна.

У меня, видите, нигде сейчас не работает курсор поинтер, подлогнул макбук, вот, ну, ничего страшного, я думаю, это ничего страшного Уже перезагружать ради видео я не буду, то есть в этом смысле, мне кажется, нету, это такая мелочь прям Так, давай увеличим размерчик немножко, да, самих вот этих штучек, то есть у нас по 25, я хочу взять по 40 В принципе, да, наверное, по 40 меня устраивает, давай посмотрим на макет, чуть поменьше, наверное, да, все-таки, где-то по 35

Вот, отличные кнопочки получились вот такие Нажимаем, открывается выпадающая формочка, в которую можно вводить все поля Красиво?

Наверное, да Давайте добавим, допустим, в процесс на согласование добавим Пишем наименование, там, давайте, так, что придумаем, давайте Рекламная кампания, рекламная Или давай, чтобы в одну строчку поместилось, давай настроить рекламу, вот так

Сумма, ну, настройка рекламы не так дорого, типа 34 тысячи рублей, e-mail какой-нибудь будет условно sportsobakasport.ru, вот, а компания будет, у нас в России есть спортмастер, давайте спортмастер укажем, о, спортмастер.

добавить ожидаем видите загрузка идет все успешно добавилось но но у нас рифе еще не произошел давайте посмотрим что у нас по нашу историю так смотри значит вот настроить рекламу мы отправляем по запрос успешно создалось все успешно пришло в ответе рассмотрим у него идет так статус to be a great все правильно статус назначился но и и смотрим сюда

Разработка тоже отработала и настройка тоже пришла.

То есть, данные все пришли успешно.

Запрос повторно ушел.

Но, видимо, какой-то баг, и оно не обновилось почему-то.

Я считаю, это реально баг, потому что такого быть не должно.

Ну, давай обновимся.

Жалко, что на вторую строчку перебросило все-таки.

Давай я так сделаю, знаешь, как... Странно, что не произошло.

Ладно, мы продолжим делать, потом еще пофиксим эту историю.

Потому что должно работать, по крайней мере.

То есть тут как бы ничего такого нету.

Но это максимально странно.

Тогда вот так див сделаем, чтобы опустилось ниже.

Короче, странно очень.

То есть, оно должно было создаться.

То есть, видели, да, причем запрос ушел повторный, данные новые пришли, но они не записались.

То есть, рендеринг не произошел почему-то, по какой-то причине.

Для меня это, конечно, интересная история.

Короче, ребята, я еще отдельно кое-что протестировал, все равно не получилось.

Короче, будем дальше уже смотреть.

Ну, я добавил, допустим, вот компанию Paint, да, покрасить стены.

Не знаю.

Как видите, да, так, кстати, приходит актуально, все прекрасно работает.

Но почему-то не переобновляется.

Ну, ладно, мы этим займемся чуть попозже, чтобы не тратить сейчас наше время на эту всю историю.

Но есть такой баг определенный.

Причем не с нашей стороны, потому что у нас все работает.

То есть как будто вот это вот ререндеринг не происходит.

Странно, почему так происходит.

Смотри, причем рефетч, он точно вызывается, потому что мы видели, что запрос идет повторный.

То есть он точно вызывается, рефетч.

Для меня пока что это загадка определенная.

Давайте здесь добавим маржин небольшой.

Мне троечка мало, я пятерочку поставлю, чтобы такой больше отступ был, более такой приятный.

Хорошо.

Так, создание сделки мы сделали, ребят.

Теперь нужно сделать перетаскивание сделки.

Что будет, мне кажется, гораздо легче делаться.

Мы уже с вами с мутацией умеем работать.

Давайте этим тоже займемся.

Так, собственно, нам нужно создать мутацию.

Давайте ее создадим.

UseMutationMutate опять же забираем.

Ключ у нас будет MoveCard, перенос карточки.

MutationFunction создаем.

Для начала давайте опишем входные параметры для этого mutation function, то есть они будут посложнее, чем обычно.

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

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

Соответственно, чтобы нам принять объект, я описываю его поля этого объекта, и тогда все работает.

Дальше, вот такие поля, Doc ID и статус, вот такой тип и вот такой запрос.

Как я и говорил, мы выполнили до этого Create Document, теперь Update Document, DB ID Collection Deals, Doc ID, конкретный IDшник нашей карточки, конкретный, да, и статус, который нам нужно, куда нужно переместить.

Вот.

Дальше, при саксессии, все, что мы делаем, это Refetch, то есть переобновление нашего каркаса.

Об этом мы уже знаем.

Давайте все импорты добавим, All Missing Imports, и, в принципе, все.

Только Mutation, он у нас, видишь, с Apollo взялся, как обычно.

Ничего нового, ребят, да?

Стандстаг у нас не работает.

Почему?

Давай я рестарт сделаю, extension host.

Что-то он стандстаг не подгружает никак вообще.

Так, view query.

Вот, теперь работает.

Все, теперь заработало.

Хорошо, есть mutate.

Когда мы его теперь будем вызывать?

Именно при переносе.

Давайте напишем функцию для drag-and-drop непосредственно.

Итак, для начала напишем функцию, которая будет запускаться при начале нашего переноса.

Функция будет называться у нас непосредственно handle.drag.start.

Она будет принимать текущую карточку и текущую колонку.

Дальше все, что будет происходить, это обновляться наше состояние.

У нас состояние, помните, вот он, drag.cart.source.column.ref, и оно будет, собственно, назначаться.

Дальше, у нас есть функция, с помощью которой мы должны выключить дефолтное поведение drag-and-drop, чтобы оно никак не мешало, никак не конфликтовало с нашим поведением.

Чтобы его выключить, все очень просто, event-prevent-default, я думаю, все знают этот базовый метод JavaScript, его максимально часто используют.

Выключаем, короче, дефолтное событие нашего переноса.

И дальше, что будет происходить при самом переносе?

Мы получаем таргет column, то есть куда мы будем переносить, в какую колонку мы хотим перенести.

Если все успешно, есть состояние, у нас текущее, то есть здесь обновилось у нас, если у нас, допустим, все занесено и оно не пустое, то в таком случае мы выполняем мутацию, передаем в нее doc.id и статус, то есть колонку куда мы будем переносить.

Вот и все.

Теперь только здесь поменяем немножечко на REF.

Да, у нас чуть-чуть по-другому здесь REF.

Вот так.

И тут тоже поменяем на REF.

Так.

Тут тоже на REF.

Хорошо.

Сохраняем.

Теперь это все нужно привязать к нашим карточкам.

Опускаемся ниже.

Вот наши карточки.

Итак, сначала пишем для нашей колонки события.

Событие drag over, как я и сказал.

Мы выключаем дефолтное поведение drag over.

И пишем drop.

Что будет происходить при дропе?

То есть при скидывании в эту колонку вызываться будет handle drop.

Как раз вот эта функция handle drop.

Где мы проверим состояние текущее и отправим мутацию на сервак.

Но перед этим мы должны инсталлировать состояние дропа самой карточки.

Точнее ее драга, то есть ее переноса.

Поэтому идем к самой карточке вот здесь и ставим состояние Drag Start, то есть начало переноса, Handle, Drag Start.

Все, больше ничего не требуется.

Все, мы как бы, мы все сделали.

Идем сюда, обновляемся, видим наши карточки замечательные.

И теперь при переносе, давайте перенесем что-нибудь, настроить рекламу, да, к примеру, в производство.

Переносим, ожидаем некоторое время.

У нас, видимо, карточка перенеслась, насколько я понимаю, но рефетч опять же не сработал.

У нас какой-то баг с рефетчем, ребята, происходит, это 100%.

Да, видишь, запрос сам ушел, но рефетч не произошел, и это печально, конечно.

Обновимся.

Да, видишь, все перенеслось.

Короче, баг у нас есть, нужно его как-то сейчас будет фиксить 100%, причем не знаю, почему он происходит, потому что код пишем такой же, как я писал и за кадром, но при этом что-то пошло не по плану, что-то не работает.

Не знаю, короче, будем постепенно с ним разбираться в любом случае.

Пока что давайте вот зафиксируем результаты, что у нас все работает.

То есть у нас все в этом плане стабильно, все переносится, просто пока что не автообновляется.

Вот переносим еще раз нашу, давай перенесем сюда.

Подождем некоторое время, обновим страничку.

Посмотрим, вот оно перенеслось условно.

Ну, то есть все работает, просто, как видите, приходится обновлять пока что.

Хотя refetch мы тоже выполняем.

То есть это проблема конкретная в React Query.

Точнее, во Vue Query, возможно, что-то мы забыли инициализировать, когда устанавливали.

Хотя навряд ли.

Ладно, мы еще это посмотрим чуть дальше.

Так, нам осталось, ребята, сделать следующие моменты.

Градиент.

Как видите, у нас, смотрите, от левой части до правой части идет градиент.

То есть сначала серенький, потом фиолетовый.

И он плавненько за счет ID перемещается.

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

Для этого перейдем в Kanban, создадим функцию.

Называется она будет «GenerateGradient».

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

Дальше мы будем использовать формат, где у нас будет интенсивность, цвета определенные, его прозрачность и так далее.

Рассчитываем сначала интенсивность цвета, насколько он будет сочный, скажем так.

Берем 100, минус, total минус 1 текущий, да, потом индекс делим на total минус 1, умножаем на 50 и отнимаем 100 непосредственно.

Дальше, step это шаг, с которым будет меняться непосредственно, шаг у меня такой, и opacity, вот, указан также opacity будет меняться.

Тут я для вас делал пометку.

Оттенок, насыщенность и светлость.

То есть как раз-таки HSL это как раз-таки вот оттенок, насыщенность, saturation.

А, я забыл, да, что H это этот оттенок.

H это оттенок непосредственно.

S это saturation, то есть насыщенность и светлость.

Это lightning.

А это альфа, то есть это прозрачность.

Вот таким образом назначаем дальше всю эту историю.

А opacity я, по-моему, так и решил не использовать в финальном варианте, поэтому я opacity уберу.

Я не знаю, зачем я его инициализировал.

Видимо, я хотел что-то сделать, но потом что-то передумал, видимо.

Да, мой оттенок... Короче, я оставлю это, не буду вырезать.

Если хотите, себе в проект добавьте эту историю.

Но я потом решил убрать.

Мне нравилось так именно больше.

Поэтому так оставлю.

Вот, теперь идем сюда.

Вот сюда идем.

И вот тут где текст-центр.

Инициализируем.

То есть style.

Через двоеточие вызываем функцию generateColumnStyle.

Опять же, ее импортировать приходится вручную.

Так, вручную, вручную, вручную.

Так, идем сюда.

Import сюда.

From...

Components, Kanban и GenerateGradient Так, идем сюда GenerateColumnStyles, прокидываем индекс нашей колонки И сколько всего элементов колонки, DataLength, сохраняемся Обновляем, как видим, у нас такая картинка получается Вот, от серого к фиолетовому, мне кажется, максимально красиво Так

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

План действий, давайте я здесь его напишу.

Так, нам, собственно, что еще надо сделать?

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

Также в нем же, в нем же comments, комментарии,

Выезжает, да, и там комментарии будут Потом Customer Table Customer Table, не забудем ее тоже реализовать Это страничка самих наших компаний И Customer Edit Page Customer Edit Page, вот Вот такая штука Вот, мне просто для себя тоже важно понять время, сколько это все займет

Так, ребят, возвращаюсь к вам с фиксом ошибки.

Покопался, немножечко подумал.

Я понял, где мы допустили ошибку.

Я ее сам, точнее, допустил.

Как видите, сейчас перетаскивание работает.

Единственный моментик, я заметил еще такую штуку.

У нас, видишь, колонки не на всю высоту экрана.

Это мне тоже не нравится.

Давай их растянем, потому что так будет лучше просто.

Так, вот наша колонка конкретно, ее нужно растянуть.

Так, сейчас покажу решение, что я придумал, точнее, что я упустил, я сам себя загнал в эту ошибку, короче Так, вот тут идет колонка непосредственно, вот, да, я сделаю class min-height-screen, вот так

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

Короче, в чем была проблема, Ред?

Проблема была максимально примитивная на самом-то деле.

Смотрите, я вернул наш вариант, который рабочий.

Давайте вернем вариант, который у нас был, вот такой.

В чем отличие, собственно, вот этой первой строки и второй строки?

На первый взгляд покажется, что различий нет, и новичок даже не поймет, в чем разница.

Это идеальный вопрос, мне кажется, для собеседования.

Вот, и я тоже попался в этот крюк, написал так, решил, знаешь, на наживу пойти сократить код, чтобы было лучше, но я потом думаю, а почему я в итоге делал раньше именно вот так, а не вот так?

А потом это меня доперло, на самом деле.

Смысл в чем?

Смысл в том, что здесь, здесь мы делаем копию Kanban даты,

Но не забывайте, что у нас есть еще один массив внутри этого массива, и к нему это не относится.

То есть это не будет создаваться как новая штука, да, это берется именно отсюда.

Поэтому, чтобы эту всю ошибку нам исправить, мы это убираем и мапимся по массиву, и каждый раз создаем здесь новый массив внутри items.

Вот в таком случае у нас все будет прекрасно работать.

Вот.

Давайте перенесем, допустим, куда-нибудь сюда.

Перетаскиваем.

Вот, ожидаем немножко, и перенеслось, как видите.

То есть все прям идеально все работает.

Ну что, ребят, возвращаемся, собственно.

У нас реализация этих четырех пунктов осталась в нашем проекте.

Давайте их реализуем.

Во-первых, нужно реализовать нам slide over comments.

Я закрою правую часть, чтобы она нам в глаза не мозолила.

Slide over comments, customer table, customer edit page.

То есть это страничка редактирования кампании, сам список кампаний, с которыми мы сотрудничаем, комментарий конкретной карточки, добавление этого комментария к карточке, обновление этого комментария в карточке.

И также выпадающее менюшка справа, да, самой карточки с подробной информацией.

Давайте начнем реализовывать.

Ну, во-первых, я, наверное, все-таки сделаю реализацию слайд-овера, да, то, о чем я сейчас... Так, подожди, давай вернем назад.

Закроем это, закрываем все.

Так, для начала нам нужно создать сам слайд-овер, да, и создать стейт.

Смотрите, я хочу, чтобы стейт был глобальный, потому что, опять же, во Vue есть такая проблема, при которой мы не можем адекватно вынести, ну, локально перекинуть просто наш стейт,

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

Реф, реф, реф, у нас не где-то в другом месте.

Короче, вот есть реф, да, к примеру, вот реф такой, реф такой, реф и так далее.

Это реактивная переменная, я про нее уже говорил вам.

Так вот, а что если мы хотим ее прокинуть куда-то более глубже, то есть в дочерний компонент?

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

Так вот, чтобы она выехала, нужно поменять ей состояние.

То есть при клике меняем состояние, и она выезжает, когда там состояние true, к примеру, да, то есть она должна выехать.

Вот, я сначала хотел сделать из локального состояния, вот просто это называется локальное состояние.

Как в обычном реакте, мы берем useState, прокидываем его глубже, используем и так далее, все без проблем работает.

Здесь тоже работает, но через такое одно место, я извиняюсь, да, то есть здесь, чтобы нам вот эту штуку прокинуть глубже,

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

То есть какие-то огромное количество махинаций, лишних действий у нас получается то, что должно получиться.

Это все невероятно сложно, и я не вижу смысла так запариваться.

Даже не то, что запариваться, это просто лишний код.

Может быть, можно это проще сделать, но опять же, я смотрел официальную документацию Vue.index.naxt, и там такого не было.

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

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

Эта пачка кода неэффективна максимально, я не вижу в ней никакого смысла.

К сожалению, как в React не получится просто взять там состояние, перекинуть и сразу автоматом оно там будет слушаться.

Такого нету здесь, к сожалению.

Поэтому переходим к другому варианту.

Собственно, для этого мы с вами, ребят, переходим... Так, куда мы с вами переходим?

Давайте состояние создадим сначала, да?

Так, для этого перейдем в Store.

Я напишу здесь deal slide, потому что такой слайд выезжает.

Store.ts создали.

Теперь опишем наш Store.

Итак, мы с вами дети уже немалые.

Да, я вас научил писать Store на примере авторизации, на примере лодинга глобального.

Мы это все с вами умеем делать.

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

И кроме этого задать туда экшены Set, Clear и также Toggle.

Toggle для изменения True либо False.

Вот такие, такой примитивный как бы опять же Store, но для вас, для новичков это идеальная практика будет.

Давайте его, собственно, опишем здесь.

Значит, во-первых,

Во-первых, мы создаем дефолтное значение.

Его описываем сразу в типизацию.

Типизация будет следующая.

Содержать в себе будет карточку конкретную.

Вот эта карточка, это и есть наша сделка.

Конкретная сделка, о которой информация будет выведена на экране.

Дальше, isOpen, то есть это открыто либо закрыто.

И, в принципе, все.

По умолчанию, карт null, open false.

Логично.

Далее, создаем сам store, инициализируем.

Export const useDLslideStore.

Define store описываем, называться он будет DLslide.

Дальше берем наш State по умолчанию, Default Value, как обычно мы делаем.

Дальше берем экшены, которые будем дергать.

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

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

И тоггл — это если мы хотим его закрыть, к примеру, либо какие-то другие действия произвести.

Так что это касаемо состояния.

И скажу вам больше, даже с учетом того, что мы решили вот такую концепцию использовать, все равно будут небольшие костыли.

Ну, потому что мы использовать будем UI-библиотечку для вот этого слайд-овера.

И она, к сожалению, принимает только локальный стейт в себя, она такую штуку не принимает, и там приходится какие-то махинации, это все будет реализовывать.

Ну, я вам покажу, сейчас все сделаем красиво и так далее.

Нам нужно сделать компонент slide over component, да, вот этот вот slide over, это выпадающая секция справа, скажем так, да, в которую будет подбрасываться карточка конкретной сделки.

Давайте ее инициализируем.

Смотрите, я долго думал над решением, как ее лучше сделать, когда этот проект разрабатывал, да, и разные были варианты.

На самом деле был вариант сделать либо самому, либо использовать UI-библиотечку.

Для этого всего я понял, что лучше использовать UI-библиотеку.

Почему?

Потому что, во-первых, для вас будет новый контент, то есть вы поймете, как работает еще одна UI-библиотека, то есть у вас будет такая большая коллекция, скажем так.

И при этом мы не потратим время на лишнюю настройку, да, выезда и так далее.

Сам выезд сделать не проблема абсолютно, да, то есть самое сложное сделать анимацию.

Не то, что даже сложно, просто очень долго по времени.

У нас так ролик получается сверхдлинным, поэтому тратиться еще на верстку не сильно хочется.

Есть замечательный Next UI.

Вот такая штука.

Мы могли бы, в принципе, все сделать на нем, на самом-то деле, да?

Вот.

Но, опять же, вы просили эту штуку заюзать, поэтому мы попробовали ее поиспользовать.

И теперь у нас есть также Nux UI.

Давайте его поставим.

Это UI-библиотечка, которую предоставляет сам Nuxt, так сказать, дочерняя библиотека его, то есть она максимально официальная и максимально вот ничего другого придумать нельзя, скажем так.

Все очень просто, ставится максимально, мы также в модули подключаем, в Nuxt все идет через модули, поэтому в этом плане можете не беспокоиться, в модули поставили Nuxt UI, сохранили, закрыли.

TypeScript там опять что-то сругается.

Тут смотрим, значит, этот модуль уже устанавливает Nux Tailwind, и ты должен удалить from your modules and dependencies.

То есть, если у тебя уже есть Tailwind, до этого стоял, да, ты его должен удалить, потому что библиотека, она его сама поставит.

Для этого давай мы так и сделаем, пожалуй, я перейду в NuxConfig, удалю отсюда Tailwind, я не знаю, насколько здесь важна очередность, скорее всего нет, но возможно важна, поэтому я лучше в самом начале подключу NuxUI.

Чтобы, если что, Shad-CN, оно использует Tailwind, чтобы оно туда его подхватило.

Ну, короче, не знаю, как это работает.

Давайте таким образом лучше сделаем.

Для большей уверенности, скажем так.

Еще прикольно есть Color Mode.

Кстати, такая штучка.

Опять же, мы ее юзать не будем.

Ну, типа, суперудобно меняет цвет фона.

Ну, не цвет фона, а цвет темы.

Не знаю, насколько она вообще адекватна.

Вот, честно признаюсь.

Ну, то есть, я имею в том плане, что это можно и через обычный Tailwind сделать.

Мне кажется, особо здесь какой-то разницы прям я не нашел.

Но, может быть, я плохо смотрел.

Но опять же, у нас сайт весь темный, поэтому особо на этом акцентировать внимание не вижу смысла.

Так, очистили консоль, это все дан, единственное еще просили удалить отсюда, да, заходим в пакет JSON, опускаемся ниже, видим Next.js Tailwind, вот просили его удалить, поэтому yarn remove, ну потому что Next.ui уже в себе содержит эту библиотеку, то есть и не имеет смысла вот так дублировать.

Давайте послушаемся документацию, сделаем так.

Дальше написано, если ты используешь весь код, ты можешь установить Intel Sense, тоже, да, если вы, ну, мы уже весь проект написали на Tailwind, да, я думаю, я просто забыл об этом повинуть, но вот чтобы у вас подсвечивались красиво классы, ставите экстеншн от Tailwind, да, это прям, ну, must have, вот, вот такая штука, чтобы вас подсказывали все классы.

Я чуть про нее совсем забыл, но я думаю, что, наверное, текущим уровнем вы уже должны были об этом знать.

Вот, и если ты хочешь больше читать, да,

Вот тут ты можешь добавить еще такую штуку, которая позволит лучше держать связь, скажем так, с этой историей.

Опять же, еще тут можно использовать экспериментальный файл tailwindconfig.ts.

То есть раньше у нас tailwind был js, теперь это ts.

Опять же, в экспериментальном формате, наконец-то они это реализовали, я давно ждал.

Но пока что я это не рекомендую использовать, потому что зачем на это тратить вообще время То есть у нас будут конфликты с вот этой библиотекой, поэтому я не вижу смысла в этом никакого Единственное, я бы вот эту штуку реально добавил Я бы пошел в... Это, как знаете, папочка будет специально для виз-кода Точка виз-код, ставим точка виз-код И в ней создаем файлик, назовем его settings.json

settings.json.

Это именно настройки точно для этого проекта.

То есть не для чего другого, только для этого проекта.

Если у вас есть код, оно реально будет работать.

Здесь, так, да, мы здесь просто ставим, что все файлы .css с таким будет расширением.

И, в принципе, все.

Это для того, чтобы у нас, кстати говоря, многие спрашивали, как убрать подсветку Apply.

Вот в таком концепции у вас теперь подсветки Apply не будет.

То есть, когда вы пишете Apply, у вас не будет проблем с этим.

Кто спрашивал, тот знает, скажем так.

Ну, либо вы, когда будете писать, поймете, о чем я говорю.

Это именно проблема виз-кода.

Так, все, с этим мы закончили.

Тут все отлично туда-сюда.

Я думаю, особого смысла дальше ничего вставить нет.

И вот мы видим дальше кучу компонентов.

Да, огромный куча компонентов в левой части.

Есть и формы, и инпуты и так далее.

Поэтому я сказал, что могли бы использовать, в принципе, одну что-то, либо это, либо это.

И когда вы будете писать свой проект, вы для себя попробуйте и эту, и другую.

Может, там третью какую-то еще попробуйте в библиотеку и выберите что-то одно.

Либо вообще пишите на своем.

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

То есть возьмите что-то одно.

Но, опять же, вот эта Shad Sien, она не считается UI-библиотекой, она как бы, скажем так, ну, она такая очень легковесная, и вы сами берете нужные вам компоненты.

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

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

Далее.

Далее мы, собственно, опускаемся здесь, и при большом количестве вот этих компонентов мы видим интересный нам компонент.

Называется он slideover.

Вот.

Есть модальное окно, есть slideover.

Модальное слишком скучно, и обычно в CRM-системах делают именно slideover, то есть который выезжает справа, поэтому мы его и будем использовать.

Нажимаем open, допустим, видишь, он справа выезжает.

Вот такая история.

Тут что, собственно?

Тут, наверное, нам больше ничего не нужно смотреть.

Да, мы сейчас сами пойдем все настроим.

Особо нам никакая помощь не требуется.

Так, здесь пока что проблемы, но это нужно перезагрузить, я думаю, среду просто, потому что у нас там некоторые были изменения большие.

Мы там удалили, там добавили.

Нужно перезагрузку сделать.

Так, ожидаем небольшого времени, инициализировал все отлично, обновляемся, должно все заработать.

Загрузка красивенько происходит у нас, и, как видишь, лоадинг, все работает.

Отлично.

Это то, что нам нужно было.

Так, идем теперь туда, куда нам нужно идти.

Так, нам нужно создать компонент slideover, да, его описать непосредственно.

Значит, у нас есть kanban, вот он kanban, сейчас components, kanban, да.

В Kanban есть вся эта информация.

Создадим новую папку.

Слайд овер.

В ней создадим новый компонент.

Слайд овер.

Точка вью.

Я также создам сразу компоненты соседние.

Это топ.

Точка вью.

Я специально пишу топ, потому что у нас название будет на основе папки.

И название будет слайд овер топ.

Поэтому я пишу сокращенно топ.

ну можно опять же можно слайдовый топ здесь принц тоже будет ok также создам сразу лейбл лейбл view да и комменты сразу создам чтобы они у нас просто были вот коммент view отлично сейчас мы это будем настраивать значит смотрите что касаемо топа лейблы и так далее да сейчас заполним соседние файлики а потом на основе них сделаем уже основной родительский компонент то есть что я имею ввиду у нас есть топ топ это вся основная верхняя часть

В нашем слайдовере там у нас будет информация о сделке расположена и так далее.

А лейбл это непосредственно обертка определенная для добавления лейбла.

То есть у нас там будет, к примеру, компания лейбл и внизу название компании.

Потом там будет стоимость сделки, внизу стоимость сделки и так далее.

Поэтому это называется лейбл.

Давайте его опишем.

Итак, создам базовую структуру.

Script, Setup, Language, TypeScript, Template, Div, Class, Margin, Bottom, Tree.

Это базовая структура, абсолютно базовая, тут ничего сложного нету.

Дальше, мы должны описать входные параметры, мы должны принимать извне, во-первых, наш слот, но слот во Vue.js описывать не нужно, слот это, помните, да, это вот мы обернули чем-то, вот условно div мы обернули, и все, что внутри, это будет являться слотом.

Потом, что нужно принять?

Нам нужно принять сам лейбл, то есть тот текст, который мы будем добавлять в качестве лейбла.

Давайте этим займемся.

Мы уже это делали миллион раз в нашем проекте, поэтому сделаем это еще раз.

Значит, сначала мы импортируем define-пропсы из view, заимпортили.

Дальше мы описываем const-пропс, define-пропс.

Далее мы указываем label-текст.

Да, и label-текст будет по типу стринга, и по умолчанию будет пустое значение.

Дальше, сам лейбл текст описываем.

Если он существует, vif лейбл текст, мы делаем класс определенный, да, то есть мы опасе 75, можем bottom 0.5, влог текст xs и добавляем в него лейбл текст с помощью двойных фигурных скобок.

И дальше мы делаем div class textbase, в который помещаем просто, ну это размер шрифта, да, а этому указываем сам слот, вот, который будем оборачивать.

Вот и все, такой простой.

Компонент у нас получился.

Я добавлю ему комментарии, да, чтобы у вас было просто представление, что и как происходит.

Я даже вот таким образом оставлю, наверное, чтобы у вас просто были комменты.

И для новичков это будет, мне кажется, круто.

Все, лейбл готов.

Давайте также сделаем с топом.

С топом чуть посложнее ситуация.

Там нужно подгрузить чуть больше данных.

Но, опять же, топ будет использовать лейблы.

Поэтому все идет по порядку.

Окей, создадим компонент пропса.

Ой, компонент топа.

Компонент топа создадим.

Script.setup.language.ts и template div и 2 diva.

Здесь обычный базовый стиль, опять же, да, из интересного, наверное, вот такая штука, у нас есть BG Black 20, это значит, что мы делаем RGB, но при этом делим на 0.2, то есть получается у нас некая прозрачность добавляется 20%.

Значит, что здесь можно добавить?

Смотрите, я как это сделал?

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

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

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

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

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

Нам никакие прописы не нужны.

Давайте этим-то, собственно, и займемся.

Для начала мы импортируем нужные нам зависимости, то есть это day.js, да, для работы с датами, там будут даты сделки, опять же.

Потом use.dl.slide.store, как раз-таки наш глобальный стор, который мы будем использовать, и из него заберем нашу карточку.

То есть use.dl.slide.store.store.

Все, а из store мы будем забирать карточку.

Вроде бы, насколько я понимаю, по документации мы не можем здесь сделать деструктуризацию, она делается немножко другим способом.

То есть если вы, условно, хотите что-то забрать, вы вроде бы это можете сделать, но это не совсем правильно будет с точки зрения Vue.js.

Оно может какие-то конфликты быть и так далее.

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

Давайте попробуем дойти его в документации.

Вот, destructure state.

Смотрим сюда.

Смотрим сюда, где-то здесь специально какое-то есть, вот, у нас есть, собственно, store to refs, вот такая штука, вы ее используете вместо нашего хука, и тогда можно, вот, видишь, и тогда можно делать деструктуризацию.

Это, конечно, не так удобно, в реакции можно и так, и так делать, ну, окей, я оставлю store просто, чтобы не париться и чтобы иметь красивый хук.

Далее, мы начнем уже делать версточку небольшую, да, делаем наименование, то есть вот тот самый наш kanban-slideover-label, вот который мы здесь создали, kanban-slideover-label.

Очень удобное название, да, генерируется, прям реально круто.

И label-text, который мы прокинули в качестве пропса вот здесь, label-text, да, вот label-text принимается здесь, мы его прокинем как наименование.

Прикольно.

Обратите внимание, что во Vue.js, если мы пишем camelcase формат, в итоге прокидываем формат через тире.

Такое интересное наблюдение.

Это будет наименование нашего сделки.

Сюда мы даваем h2 обязательно, да, и пишем store cart name.

Вот отсюда берем со store cart name.

То есть название нашей сделки.

Ну, либо карточки, без разницы.

Сумму дальше делаем, slide over label, опять же, оборачиваемся лейблом, пишем сумму и конвертируем currency.

Мы это уже умеем делать, делали мы это все в карточке.

Далее.

Кстати, что интересно, он почему-то импорта не просит, да?

То есть неужели оно будет и так работать без импорта?

Вот этот интересный моментик, оно, по-моему, будет без импорта даже работать.

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

Короче, Naxt это сплошная магия, как и Vue.js в целом.

Дальше делаем статус.

Статус я хочу сделать в бейджике, в красивом, поэтому делаю UI-бейдж.

А бейджа у нас нету, кстати говоря, да.

Мы бейдж еще не создали, давайте сейчас бейдж создадим.

И StoreCardStatus.

Вот.

И остался последний клиент.

Клиент будет StoreCard Company Name.

И дата создания.

DGS StoreCard Created Ad.

Все.

Это дата создания будет.

Опять же, вы ее уже делали.

Так.

Сохраним.

Давайте сделаем бейджик наш.

Не забудем его реализовать.

Так.

Возвращаемся назад.

Сюда, сюда, сюда, сюда.

Сюда, сюда, сюда, сюда.

Так.

Вот.

Значит, у нас есть EDD Card.

Мы делаем EDD Bage.

Bage.

И создаем наш бейдж.

С вариантом Outline.

Все, создались.

Отлично.

Так, с этим тоже закончили.

Давайте я сделаю рестарт экстеншенов.

Что-то мне показалось, что у меня как-то подлогнула среда и какие-то типы пропали куда-то.

Вроде бы сейчас все стабильно, все нормально.

Закрываем.

Короче, такое чувство, что мы как будто реально не можем и не писать импорты в каких-то местах.

Но пока же для меня это загадка.

Я к такой магии не привык после реакта.

Так, это понятно, да, то есть с этим мы закончили, тут в принципе понятно.

Теперь, касаемо слайдовера, давайте займемся непосредственным, сделаем скрипт, скрипт language.ts и setup, ну классика жанра, жалко сниппетов не хватает под эту историю все, и тут делаем темплейт, окей, это наше выезжающее меню, напомню, да.

Значит, смотрите, здесь мы хотим использовать глобальное состояние.

Помните, я про него вам говорил?

Глобальное состояние, которое мы с вами сделали.

Вот он, you сделал слайд-стор, вот эту штуку.

Но, так, давай я это, кстати, уберу.

Вот так, да?

Но сам слайд-овер, который мы будем юзать из библиотечки NextUI, он в себе не подразумевает использование такой штуки.

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

Как это сделать?

Смотрите, как я и говорил, во Vue это все делать через определенную махинацию.

Давайте сейчас возьмем Store просто.

Ну, тут, я думаю, классика жанра.

А теперь самое интересное.

Мы должны с вами с помощью функции computed...

Все это дело вычислить и написать.

Значит, const.

Теперь локальное состояние isLocalOpen равно computed.

Открываем объект.

И здесь нужно написать getter и setter.

Пишем getter.

Getter будет отдавать наш store.

Ну, просто идем store.isOpen, получается.

И setter, как я и говорил до этого, мы уже разбирались с вами, он будет назначать что-то.

Мы берем текущее значение, которое будет приходить в этот setter.

И будем его просто менять.

То есть мы store is open будет равно value.

Вот таким образом.

Все.

И теперь мы перевели из этого состояния в такое состояние.

Но это выглядит гораздо комфортнее, нежели мы прокидывали бы еще профсовое состояние, потому что там было бы еще хуже.

Там через emit нужно было делать.

Короче, это все гораздо сложнее.

Таким образом, просто через компьютер, через getter и setter все сделали.

То есть getter нам позволяет получить из нашего store значение, а setter позволяет нам его перезаписать.

А на самом деле из localopen будет просто считаться обычной реактивной переменной, и даже библиотека не будет понимать, что мы используем уже другой сторонний стор.

Давайте мы перейдем в комментарии.

Я тут тоже сделал небольшую сразу же вот такую историю.

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

Так, все, теперь можно описывать slide over, значит, пишем UI, slide, так, slide over, вот такую штуку пишем, да, и здесь пишем vmodel, vmodel краски для связи наших перемен, ну, мы, опять же, с вами это уже умеем делать, из local open, вот таким образом, все, и при таком кейсе, как бы, да, и внутри описываем уже всю нашу, весь наш слот, так называемый,

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

В этом нам очень сильно помогают функции Computed, которые позволяют нам это все дело так обернуть и как бы обмануть немножечко нашу библиотеку Nux UI.

Сделаем небольшие настройки для DIVA внутри.

Я обычную карточку буду использовать, которая у нас уже есть, вот, только поменяем ее немножко стилистику.

Сделаем такие флексы.

Потом UI.

С помощью этого UI можно менять карточку.

А, нет, эта карточка другая, ребят.

Эта карточка идет из Next UI непосредственно.

Не перепутайте.

Здесь, видите, написано U-карт, а там у нас UI-карт.

Так что будьте внимательны.

Немножко другая история.

Я меняю просто какие-то определенные стили под наш дизайн, который в итоге получится.

Тут что интересно.

Чтобы нам правильно занести, у нас есть определенные секции.

Сначала секция Header.

Пишем Template.

Template — обычный тег, да?

Но в нем мы можем через решеточку вот тут написать header.

Это является нашим хедером.

И в хедере у нас будет, значит, kanban...

Слайдер.

Слайд овер.

Топ.

Вот.

Опять же, наверное, более правильным сетом было здесь не топ, а хедер.

Да, у себя в проекте, наверное, можете поменять на хедер.

Я что-то решил топ.

Наверное, хедер будет более правильным с точки зрения наименования.

А здесь уже ниже комментарий.

Канбан.

Слайд овер.

Комментс.

Вот.

Все.

Сгенерировали.

Готово.

Сохраним.

Так, давайте вспомним, где у нас еще что-то не сделано.

Комментарий мы еще чуть позже сделаем.

Давайте перед комментарием мы все-таки откроем, посмотрим, что получилось у нас в целом.

Для начала мы перейдем в IndexView и назначим наш слайд-овер в место, где это должно быть.

Значит, добавим его вот сюда.

То есть это будет у нас идти после DIVA, смотрите, после DIVA с гридом.

Грид заканчивается, добавляем эту штуку.

Вот, на самом деле, где ее добавить, разницы особо нету.

Вот, пусть она просто будет здесь.

Так, давайте попробуем это сделать.

Так.

Или у нас еще не получится.

Подождите, мы состояние, да, по-моему, еще не меняем.

Да, мы, по-моему, еще не открываем саму карточку.

Да, давайте перейдем, значит, в индекс U. И мы забыли здесь вверху назначить.

Так, давайте какой-нибудь... Ну, я привык здесь сделать, да.

U сделать слайд-стор обязательно, да.

И его нужно импортировать.

Его импортировать нужно...

Импорт лучше взять отсюда, откуда-нибудь.

Так, сюда, сюда.

Кстати, импорт ты можешь тоже сортировать красивенько.

Я думаю, притер поставить какой-нибудь, накатить.

Хотя не знаю, он работает с UGS или нет, как в React.

Ну, посмотрим.

Так, Store.

Дальше берем и идем сюда.

Нам нужно найти с вами именно хедер.

Так, UICardHeader, вот эта штука.

RollButton выставляем, как будто является кнопкой, да.

И через собачку клик.

Видишь, опять подсказок нет, к сожалению.

Пишем Store, точка.

И тут указываем Toggle.

Точнее тоггл будет неправильно сделать, да, тоггл нам не нужно открывать из-за карты, нам нужно указать store.set и в нее назначить карту определенную.

Вот наша карточка.

Вот store.set.card.

Отлично.

Сохраняем просто.

Нажимаем на что-нибудь карточку, вот открывается условно наш слайдовер.

То есть разработка сайта, вот он открылся, разработка сайта, 500 тысяч рублей, входящий альфа-банк.

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

Вот.

Так что вообще кайф полноценный.

Все работает, закрывается красивенько.

То есть штука прям реально крутая.

И также в ней нам осталось взять комментарии чуть ниже.

Комментарии уже будут работать с сервером напрямую, потому что нам нужно их переобновлять, добавлять и так далее.

Поэтому нужно будет поработать немножко с ViewQuery.

Так, с этим закончили.

Это мы закрываем.

Теперь поработаем с комментариями.

Наши комментарии замечательные.

У нас есть store deal.

Создадим здесь два хука.

Я хочу их сразу вынести, чтобы было просто комфортно нам это все делать.

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

New file, use.

Давайте назовем use comments.

By deal, ну можно на самом деле просто use comments, по большому счету у нас проект очень маленький, короче, вообще на самом деле можно просто комменты сделать, use comments, по той причине, что проект маленький и у нас комментарии только в одном месте, да, то есть в нашем проекте это допустимо, конечно, в больших проектах это недопустимо, потому что комментарии бывают разные, то есть бывают в одном месте, в другом, давайте я ставлю просто use comments, просто, ну могу себе это позволить, скажем так

Напишем функцию export function use comments by deal, опять же я сделаю просто use comments, дальше мы делаем use query, берем use view query, классика, потом берем collection deals dbid, потом берем store наш замечательный.

Берем карт ID, сторону для чего нужна, да, для того, чтобы мы из нее вытащили карт ID.

Берем stored, у него берем карточку, IDшник, все.

По карт ID дальше можем производить действия.

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

Дальше выполняем hook use query и сразу возвращаем все, что он нам будет отдавать.

Здесь, собственно, мы делаем query ключ.

Опять же, как я вам говорил, вы пишете сделка и через запятую id-шник.

Тогда у вас в кэше будут храниться именно конкретные разные ячейки под разные id-шники.

Это удобно.

Дальше query function.

Делаем стрелочную функцию db getDocument, dbid, collectionDeals, cartId.

Вот такую штуку делаем.

Это для получения документа.

То есть мы получаем конкретный документ.

Не лист документа, как у нас был до этого, а конкретный getDocument.

DB ID, Collection Deals, ID нашей коллекции баз данных, то есть таблицы, и ID нашей конкретной карточки, чтобы ее получить.

Вот, посмотрите внимательно, да, то есть мы получаем именно нашу карточку, то есть мы не получаем комментарий по карточке, мы получаем именно нашу карточку, так гораздо легче, особенно в AppWrite.

Собственно, и все.

Вот такой простой компонент получился у нас.

Зато мы декомпозировали логику, и она лежит прямо отдельно.

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

Я рад, что во Vue.js пришли хуки, как в React, и теперь здесь все можно делать супер красиво и нарядно.

Так, это закрываем.

Теперь создадим новый компонент.

Я назову его useCreateComment.

UseCreateComment.

Или useCommentCreate, более правильно было бы сделать, я не знаю.

Это тоже, как бы, опять же, как вам будет удобнее.

Наименование это дело такое, как бы, под вопросом.

Так, и здесь по мне, кстати, верну, да, useComment все-таки, вот так не забудем сделать.

Давайте создадим этот новый хук замечательный.

Итак, создадим нашу функцию export function useComment mutation.

Далее импортируем UseMutationsViewQuery, дальше импортируем наш UI ID.

Я вот так сделаю, просто V4 мне не нравится.

Дальше мы с вами возьмем Collection Comments DBID, то есть это для того, чтобы делать запрос к базе данных опять же.

И берем Refetch дальше, и он по типу будет Refetch void.

Все, Refetch нужен для переобновления данных, то есть когда мы взяли и переобновили.

Смотрите, какой нюанс.

В React, допустим, вместо вот этого прокидывания Refetch'а можно использовать QueryClient.

Здесь он не заработал у меня почему-то во Vue.js или конкретно в Nuxt'е.

Не знаю почему, то есть вы пишете обычно в React const QueryClient, use QueryClient.

Дальше берете этот QueryClient и у него есть замечательный метод, называется он InvalidateText, InvalidateSquares.

Ставите массив, указываете ключ и все, он инвалидирует данные.

Там ID-шник какой-то условный.

Но во Vue он не заработал, поэтому мы будем через старый добрый Refetch, будем его прокидывать в качестве пропсов и использовать.

Так, окей, дальше мы берем наш Store замечательный.

Дальше создаем реактивную переменную comment.ref.

Для чего она нужна?

Для того, чтобы мы получили наш текущий текст из нашего комментария.

То есть тот комментарий, который мы хотим отправить в базу данных.

То есть это прямая связь с input.

Дальше берем мутацию.

Берем ключ мутации, называем ее add comments и указываем, соответственно, значение определенное.

Тем самым у нас будет, опять же, разделение на разные комментарии в нашем кэше.

И функция.

Функция мутации.

Здесь мы создаем create document, dbid, collection comments и id-шничек.

А дальше мы в объекте указываем текст нашего комментария и, кроме этого, сделка, которой будем привязывать наш комментарий.

Все очень просто.

То есть мы привязываем конкретные сделки с помощью ID-шника, да, и вот мне этим нравится прям вот этот app-write, максимально все просто и примитивно, и реально очень удобно.

И при успешном запросе, когда все уже добавилось в базу данных, мы должны переобновить наш комментарий, то есть сделаем refetch.

В конце очищаем наше поле.

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

При нажатии на Enter я хочу сделать, то есть кнопку не хочу реализовывать, хочу сделать так, чтобы при нажатии на Enter в нашем input происходило добавление комментария.

Поэтому вот наша функция, которая будет выполняться при нажатии на кнопку Enter.

Тут мы пишем, если комментария нет, то просто возвращаем, то есть нужно, чтобы значение было не пустым.

Дальше выполняем mutate.

И в принципе вот наш mutate берем отсюда и сохраняем.

И осталось еще сделать возврат.

Да, возврат переменных.

Нам нужно вернуть лишь два момента.

Это саму функцию, которую будем использовать.

И comment.ref непосредственно.

То есть комментарий для привязки.

Вот такой компонент у нас получился.

Я поменяю еще раз, повторюсь, UID на обычный UID.

Вот таким образом, чтобы было покрасивее.

Тут напишу useCreateComment.

И сохраню просто.

Давайте быстренько пробежимся.

В принципе, так, он маленький, я подробно все разобрал.

Давайте еще раз быстренько пробежимся.

Значит, берем Refetch для перезагрузки данных, чтобы мы, когда добавили новый комментарий, все переобновились, получили новый список комментариев.

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

CommentRef для того, чтобы связаться с инпутами, получить значение текущего инпута.

выполняем мутацию ключ для того, чтобы хранить в кэшировании правильно в кэше, функцию для того, чтобы отправить на запрос на базу данных, createDocument, dbid, id-шник базы данных, id-шник нашей коллекции комментариев, id-шник генерируем автоматически, это просто рандомный id-шник, абсолютно рандомная строка, это требует сам этот app.write, и в качестве body прокидываем сюда текст и дел.

В конце мы при успешном запросе все подчищаем и все обновляем.

Дальше саму функцию делаем для написания комментариев.

Здесь просто идет проверка, точно ли у нас есть значение, которое мы хотим отправить.

Все, если нет, то возвращаемся.

Если есть, то отправляем запрос в базу данных.

Все.

С этим тоже мы закончили.

Давайте сразу сходу я вставлю все импорты для экономии времени.

Только эти импорты у нас теперь другие будут, поэтому не совсем прям получится экономить.

Ну ладно.

Пишем const и берем хуки, нужные нам.

Это use comments.

Use comments.

Так, хорошо.

Первый раз у меня во Vue сработал импорт, ребят.

Можете с этим поздравить.

Use create comment.

Отлично.

Здесь мы забираем поля следующие.

То есть забираем дату.

Забираем refetch.

Refetch, да?

И изloading.

Изloading.

Вот так.

Здесь отдаем наш refetch.

Только отдаем им в объекте.

А здесь мы забираем comment ref и write comment.

Все очень просто.

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

Консткарт, дата как unknown, unknown, прошу прощения, и как сделка.

Вот мы это уже делали опять же с MData, потому что AppWrite не позволяет нам нормально типизировать, в этом плане, да, вообще он круто типизирован, но вот в этом плане это нормальная история для таких библиотек.

И

Мы должны просто сами подогнать тип, который реально здесь будет нужен.

Вот, я его подогнал таким образом.

Берем карточку.

Теперь давайте напишем версточку небольшую.

Тут все максимально просто.

Мы берем UI input, placeholder, оставьте комментарий, и vmodel, связь, помните, с comment ref.

Это обычно связь, которую мы уже делали.

А вот тут самое интересное.

Event, как нам сделать, чтобы при нажатии на Enter вызывалась функция write comment?

Пишем собачка.

Key.

Никаких подсказок я не вижу.

Давай restart extension host мы сделаем, перезагрузим все.

Подсказки должны быть.

А подсказок тут и не будет, да, у нас получается?

Это вот проблема view, мне кажется.

Да-да-да, смотри, подсказок мы не увидим.

Вот такая история, ребят.

Подсказок мы реально не увидим, потому что это view.js.

Да, то есть, как видишь, у нас хоть и UI input, по идее, они, наверное, должны были описать все правильно, я думаю.

Тогда посмотрим.

Props и define props, define image, use view model.

Ну, хотя, знаешь, здесь тоже особо ничего не описано.

Короче, в React мы могли условно экстендить спокойно, да, от самого input и иметь те же самые атрибуты.

Здесь такого нету.

Поэтому будем вспоминать и переписывать.

KeyUp здесь идет.

KeyUp.Enter.

И указываем здесь Write Comment.

Write Comment.

Вот таким образом.

Все.

И видишь, она даже сейчас не работает, хотя на самом деле она работает.

Вот такая тема забавная.

То есть при нажатии на Enter будет происходить вызов вот этой функции Write Comment, которая будет отправлять мутацию на создание непосредственно.

Далее давайте мы сделаем скелетон.

Скелетон, собственно, это вот эта штука.

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

Так, а что у меня не скролится?

Давай обновим.

Так, что-то у меня подлогнул.

Точнее, у них сайт подлогнул.

Берем скелетон.

Вот он, скелетон, замечательно.

Когда он так красивенько подгружается, все.

Давайте его возьмем в наш проект.

Ну, чтобы комментарии красиво грузились, почему бы, собственно, и не сделать это Так, давай сразу, оп, перейдем дальше, Skeleton, Skeleton, так, Skeleton, вот таким образом И там, по-моему, нужно поменять нам стиль, если я ничего не путаю, сейчас я посмотрю А, нет, все, все отлично, все отлично, в рамках разумно, окей Так, нам перезагрузить нужно, иначе работать не будет, напомню, да, вам, у нас что-то с приложением, какая-то беда И можно, в принципе, использовать Итак

Давайте сначала сделаем сам скелетон, UI-скелетон, если загрузка идет, это загрузка, тогда мы его показываем Класс VFull по ширине, по максималке, высота 76 пикселей, округление и вот такая история Дальше, иначе показываем карточку, то есть это не просто иначе, а это else if, то есть иначе если, иначе если карточка присутствует

то мы можем показывать сами наши комментарии.

Мы дальше берем див, по циклу пробегаемся, то есть цикл на основе карт, комментов, в комментариях нашей сделки конкретной.

Берем каждый комментарий, у него айдишник будет являться ключом.

Дальше иконка, вот такая иконочка, ну это я просто сделал по приколу, там слева будет такая икона красивенькая.

Я думаю, может 25 даже поставить здесь, шрифт.

Далее мы с вами сделаем вот такую историю, да, это сама обертка для комментария и сам комментарий.

Комментарии, его дата, day.js нам нужно сейчас будет импортировать, и также сам текст комментария.

Вот такой простой компонент получился.

Так, day.js нужно импортировать.

Неужели ребят импорты заработали?

Да ну?

Давайте вместе поаплодируем в UGS.

Так, и формат видите, да, какой у нас даты?

Только время, то есть 2h, то есть время, да, и 2m маленькие, то есть минуты.

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

Нажимаем Enter.

Ждем некоторое время.

Вот он появился.

Позвонить клиенту в понедельник.

Пожалуйста.

Все прекрасно работает.

Можно еще один комментарий задать, допустим.

Я не знаю.

Подготовить договор.

Или как?

Договор подписан.

Договор подписан.

Нажимаем Enter, отправляется, вот договор подписан.

И видишь, время у нас меняется непосредственно.

Круто, я считаю, что да, круто.

Тут видишь, тут тоже грузится, тут их пока что нет, тут грузится, тут они есть.

Все, то есть у нас полностью баз данных все синхронизировано.

Отлично, ну можно даже в BD-шке посмотреть, кому это интересно.

Вот коммент заходим, потом вот наши документы, вот наши комментарии.

Все идеально работает.

Окей, ну, собственно, вот такую мы большую часть сделали, slide over, да, мы с вами полностью сделали выпадающий слайд, мы назначили там комментарии, мы назначили там информацию о нашей сделке конкретной.

Короче, получилось прям максимально круто все.

А, ну, мы сразу и комментарии закрыли с вами, так что мы красавчики.

Осталось два моментика, да, это customer table сделать и customer edit page.

Вот, и, в принципе, все, проект можно заканчивать, проект такой прям очень сочный получился.

Ребят, лайки проставьте, если вдруг еще не прожали, это очень важно для продвижения канала.

Давайте займемся.

Сначала сделаем Customer Table.

Самое такое простенькое.

А потом Customer Edit Page.

Тут причем у нас будет также загрузка изображений.

Не забываем про этот момент.

Тоже будет интересно посмотреть.

Создадим новую папочку Customers.

В ней создадим, собственно, файлик.

Можно перенести Customer сюда.

То есть мы понимаем, да, ротинг как можем делать.

Мы можем ротинг делать условно.

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

Либо можем делать еще под папку, для того, чтобы мы в нее могли положить другие, ну, следующие пути.

К примеру, я хочу потом получить путь slash customers, slash edit, slash id, поэтому я делаю папку customers, в ней делаю индекс, означает, что этот файл будет открываться, slash customers, и все, да, дальше делаю папку edit и так далее.

Значит, это понятно, да?

Здесь давайте сначала мы сгенерируем компонент.

Нам потребуется компонент новый от наш Shad-CN.

Будет он называться Table, Table, Table.

По-моему, Table он и называется Table.

Короче, табличку нам нужно сделать, чтобы она у нас красивенькая, нарядненькая была.

Мы ее смогли вывести красиво.

Она создана успешно.

Давайте, собственно, этим и немножко займемся.

Так, для начала сделаем скрипт, как обычно.

Ничего нового, в принципе, скрипт.

Жалко, что нет сниппета, почему-то, опять же, повторюсь, который мне бы вот это все описывал, чтобы я каждый раз не писал И импортируем базу, ну, определенную базу просто Это SEO-метод от заголовок страницы Давайте сделаем уже по канонам CRM-систем Опять же, это не совсем круто, то есть лучше это вынести в отдельную утилитку, да, и просто оборачивать функции, чтобы у вас каждый раз прибавлялась эта штука

Но я оставлю таким образом.

Или, может быть, здесь, как в Next.js, есть тема, когда ты на уровне layout оказываешь шаблон, его можно переиспользовать.

Возможно, есть.

Опять же, для нашего проекта это абсолютно не важно.

У нас SEO-оптимизации в проекте нет.

Это у нас полностью клиентское приложение.

Потому что мы делаем админ-панель.

Так, это мы все подхватили.

Далее.

А дальше нам потребуется подгрузить данные.

Опять же, подгружать данные мы уже умеем.

Берем просто const, берем useQuery.

Тут максимально все примитивно, на самом деле.

Дальше берем query function, query key, ключ у нас будет непосредственно customers, подгрузка всех клиентов наших, а query function будет стрелочная функция, db, потом list documents и подгружаем все документы, то есть db id и collection deals.

Точнее, Collection Customers.

Все.

Вот такой максимально примитивный вариантик мы сделали с вами.

Вы там уже на свое усмотрение можете добавлять что угодно.

Напомню, что здесь можно прокидывать query-запросы.

Вот, смотрите, Collection ID.

Вот, queries можно прокидывать.

Как узнать, какие queries вообще существуют?

Переходите в документацию.

Как я и говорил, она идеальная.

На мой взгляд, переходим в документацию и пишем в поиск.

Ну, реально, документация очень крута.

Она легенькая, простенькая, вообще то, что надо.

Пишем в query.

Queries.

Вот queries.

Смотрим сюда.

Вот посмотрим, что у нас есть в queries, какие у нас штуки можно сюда прокидывать определенные.

Файлы и так далее.

Вот, тут можно опуститься.

Вот есть order, то есть сортировка, есть поиск.

Поиск спокойно работает.

Оффсеты, лимиты для погенации.

Все это здесь присутствует.

Вот, пожалуйста, документация.

Чекайте, пожалуйста, без проблем.

В этом плане мне очень сильно нравится АПП Врайдзу, в отличие от того же Страпи ужаснейшего, от Супа Бейза и так далее.

Вообще, просто моя конфетка.

Это пока что мой фаворит из готовых БД, которые только можно взять.

Я невероятно доволен.

Так, все, мы сделали.

В принципе, о скрипту больше нечего писать.

У нас есть все, что нам нужно отсюда.

Кастомеры мы сейчас используем.

Опять же, нам это потребуется кое для чего.

Чтобы нормально можно было все это дело вывести.

Ну, опять же, чтобы, наверное, еще облегче, чтобы мы в HTML не писали этот код, я опять же здесь обозначу, что у нас есть const customers равно data.documents.

Вот это data.documents, да?

Да, .value, .documents, вот, и обозначим, что это является у нас нашим кастомером.

Все.

Теперь кастомер — это кастомер.

Все.

Ну что, просто эту логику, чтобы не писать ее внутри HTML.

Итак, далее мы делаем, ребята, с вами div class padding-10.

Ну, это классика, это на каждой странице у нас есть.

Опять же, ваше домашнее задание будет вынести вот heading в отдельный UI и его переиспользовать.

Без проблем, потому что у нас он часто используется, именно вот такие стили.

для заголовков далее мы делаем собственно загрузчик если загрузки есть пишем в loading дальше иначе мы показываем нашу таблицу в таблице у нас есть header верхняя часть таблицы да у нее есть table row ну header это там где заголовки каждой колонки table head первое это изображение колонка вторая колонка это будет наименование третья колонка это будет email и четвертая колонка это будет откуда пришел клиент все основные поля которые нам нужны специально выставил ширину чтобы они ну более менее компактно смотрелись

Дальше у нас есть table-body, а table-body, то есть есть заголовок у таблицы, ну как бы не заголовок, а верхняя часть шапка, а есть body, то есть его контентное содержание.

В body у нас есть table-row, и в каждом row мы делаем, пробегаемся по циклу, да, с помощью, опять же, директивы v4.

Тут можно поменять будет, сейчас мы поменяем это, да, ключ у нас будет customer-id, то есть мы берем customer, customer-id это будет ключом являться,

Потом table.cm, конкретную ячейку мы уже описываем.

В ячейке у нас будет ссылка, ссылка на картинку.

То есть первая будет у нас картинка непосредственно, да.

И вторая у нас непосредственно будет customer name, customer email, customer from source и в принципе все.

Вот основные четыре, то есть картинка.

Опять же, картинку можно поменять на next image, на ваш вкус и цвет, как бы можно так сделать без проблем, тоже будет работать.

Потом

Тут у нас название компании, email компании и откуда она нас нашла.

Собственно, путь, по которому будет открываться непосредственно сама ссылка, customers.edit.customer.id.

Это путь, по которому будет происходить открытие формы, где будет происходить редактирование нашей компании.

Давайте, чтобы не было вординга, во-первых, я вот это заменю на customers.

Мы поменяли, да, мы облегчили эту историю, сделаем вот так лучше.

Гораздо лучше смотрится.

И давайте создадим страничку вот эту сразу же, потому что у нас View будет ругаться.

Берем Customers, New Folder, напишем Edit.

Мы же хотим сделать Slash Edit.

И дальше мы в квадратных скобках делаем ID.

Это означает, что он будет динамичным, то есть мы его будем брать из самого URL.

Это будет являться нашим ID-шником.

А здесь мы будем передавать Customer ID.

Сохранимся, еще раз просмотрим, что мы сделали.

Заголовок для страницы, подгрузили данные сервера, list documents, dbid collection customers.

Опять же, подробно не вижу смысла, мы это уже разбирали прям очень много раз.

Взяли дальше customers, обозначили, назначили просто тип по факту, чтобы у нас была типизация здесь.

Заголовок кинули, загрузчик кинули, иначе показываем саму табличку.

Дальше кидаем заголовки каждой колонки.

Потом мы берем Table Row, его также с помощью цикла.

Используем цикл для того, чтобы показать наши все элементы.

И дальше используем Next Link и Next Image.

Вот это классика, собственно.

Потом у нас идет Table Cell.

И, в принципе, все.

Это наши ячейки.

Сохраняемся.

Сохраняемся.

Дальше.

Дальше давайте посмотрим, что у нас получилось вообще в целом.

Для этого перейдем в Customers.

Вот Customers.

Наши клиенты грузятся.

Подгрузились, но самой таблицы нет.

Потому что, помните, нам нужно перезагружать каждый раз наш Next и обновлять кэш именно, чистить кэш.

Иначе компоненты почему-то не переобновляются.

Ожидаем.

Это потому что мы новый компонент создали с вами.

Так, ошибка, потому что этого нету компонента.

Давайте его сделаем базовым.

Так ожидаем.

Наши клиенты грузятся, грузятся.

Вот, пожалуйста.

Клиентов пока что нет.

Хотя странно, клиенты должны быть у нас.

Давайте посмотрим консоль.

Посмотрим нетворк, что у нас здесь происходит.

Еще раз обновимся.

Документы мы получаем, как видите.

Дальше документы приходят.

Вот, name alphabank.

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

Тут переименуем на customer сразу.

Вот так попробуем сделать.

Так, посмотрим.

Ничего пока не вижу.

И ничего не отображает, собственно, у нас.

Так, что логично, да?

Почему так работает?

Давай мы чуть по-другому сделаем.

Блин, неужели придется вынести на уровень этого?

Давайте, ладно, вынесем на уровень тогда вот здесь.

Хорошо, без вопросов.

Придется так-то, ребят, все-таки сделать, чтобы это было покрасивее.

Так, а что с customer?

А, почему импорта не было?

Странно.

То есть мы берем отсюда кастомер, здесь переназначаем тиф внутри HTML просто для удобства и посмотрим.

А, и прям вообще ни в какую не грузит, да, смотри.

Кастомер.

Ага, вот так нужно взять.

Кастомер.документ.

Вот так, наверное, все-таки более правильно будет сделать.

Давай обновимся еще раз.

Да, мы чуть неправильно тип описали, просто забыл учесть, что у нас есть документы.

В этот момент забыл учесть.

Так, все окей.

Хорошо.

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

Все, то есть у нас вот они, клиенты наши замечательные, все присутствует.

Она такая компактнее получилась табличка, чем в этом, в демке, потому что...

Ну, я выбрал вариант другой стиля, помните, да, мы выбрали стиль Нью-Йорка вместо дефолтного стиля Ну, мне так даже больше нравится, честно признаюсь, мне так реально нравится даже больше Можно подредактировать, опять же, там, ну, что угодно, можете это все дело подправить Здесь всякие есть паддинги различные и так далее Вот, я это делать не буду, у меня таблица полностью устраивает функциональность, поэтому я оставлю таким образом Так, что дальше?

Что дальше, что дальше, что дальше?

Ну, можно для изображения только чуть побольше сделать, типа 100 пикселей, вот таким образом

Вот, чтобы чуть больше просто было мест.

Даже, может быть, 140 у нас места прям много.

А, ну, опять же, да, не обновиться, скорее всего, нужно обновить полностью, опять же, чистить кэш.

Не знаю, в комментах, ребята, напишите, в чем у нас произошло с приложением.

У нас, типа, постоянно теперь при изменении именно UI-компонентов приходится перезагружать и очищать кэш, я не знаю.

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

Так, давай посмотрим.

Кстати, смотрите, в хроме какая-то модная штука Open Read Modding.

Я не знаю, я никогда не пользовался, кстати, Read Modding в хроме.

Да и вроде сейчас так же.

Короче, ладно.

Окей.

Суть в том, что все вот.

Здесь можно накинуть потом в качестве домашнего здания погенацию какую-то.

Можете сделать фильтрацию, если вам нужно.

Сортировку по полям по клику, да, можете сделать.

И причем вам это позволяет делать, по-моему, сам ShotCN.

Пожалуйста, идете в «Table».

Как вот, видите, вот такая штука, опускайтесь ниже.

А нет, я вас обманул, подожди, нету, да, что ли, здесь?

Мне казалось, что есть, окей.

Ну, сами можете сделать, собственно, это не проблема.

По-моему, в NextUI, по-моему, я смотрел, там была такая штука.

Да, вот здесь есть фильтрация, видишь?

Вот, можете от них взять, в принципе.

Хорошо, это мы разработали.

Дальше, дальше, ну, во-первых, давайте поля побольше сделаем.

Что-то мне прям совсем тесно здесь, реально.

Вот так, да, я, наверное, сделал.

Все, что-то прям тесновато.

Ну и все-таки можно, знаешь, можно все-таки вот тут для table row класс прописать margin-top 2.

Даже не margin-top, знаешь, а padding padding-y.

Но он, скорее всего, не обозначен.

Давай посмотрим.

У нас, видишь, padding внутри каждой дэшки идет.

Каждая TD-шка, это паддинг у нас идет.

Поэтому этот паддинг, он как бы... Ну, тут просто нужно больше указать, чтобы он сработал.

4, допустим.

А, ну видишь, все равно не отрабатывает.

То есть это нужно в каждой именно ячейке указывать.

А в каждой ячейке table cell непосредственно.

Давайте перейдем сюда, попробуем так сделать.

Table cell.

Вот сюда, вот сюда.

Вот, паддинг 2, да?

Давайте паддинг 4 сделаем.

Теперь обновим, я думаю, этого нам хватит вполне.

Хотел не менять, в итоге поменял, да, все, чтобы покрасивее было.

Так, ожидаем, загрузка, ожидаем, ожидаем, все, вот теперь идеально, теперь как в демке было все отлично.

Так, кликаем на какую-то картинку, которую хотим обновить, давайте спортмастер, и пока что пустая страница.

Давай теперь займемся, собственно, мы эту историю сделали, займемся теперь, вот мы это сделали, займемся теперь Customer Edit Page, редактируем самого кастомера.

Так, для этого перейдем в Edit Page, да, непосредственно вот сюда.

Тут подхватим сразу все импорты, которые нам потребуются.

У нас это будет по импортам.

Сейчас расскажу.

Скрипт давайте допишем.

Значит, смотрите, нам потребуется здесь Storage, да, для того, чтобы мы могли записать наш файлик, который мы загрузим, загрузку файла сделать на сервер, поэтому берем Storage.

Берем Use Mutation, Use Query.

Давайте напомним, что такое Storage вообще в целом, да.

Сейчас я app.write.

Вот, Storage — это вот такая штука.

Мы создаем просто инстанс от Storage и используем его.

Все.

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

Дальше берем, опять же, константы, нужные нам для запроса, и берем тип кастомера.

Все, это база.

Дальше, смотрите, нам, чтобы...

Я думаю, как бы правильно сделать.

Наверное, мы чуть попозже сделаем загрузку.

Сначала мы, наверное, сделаем все остальное, потом загрузку сделаем, чтобы было просто более правильно.

Значит, смотрите, нам нужно сначала создать с вами интерфейс.

Как помните, мы с вами делали у формы, где мы делали?

Формочку сделки, помните, мы с вами делали?

И мы там брали уже готовый интерфейс, IDL, и из него с помощью пика забирали нужные нам поля.

То же самое сделаю здесь.

Мы берем iCustomerFormState, с помощью пика из Customer забираем аватар URL, email, name и from source.

Я опять же повторюсь, я мог бы забрать это с помощью омита, то есть омитом вырезать вместо пика написать омит и указать здесь поля, которые будем наоборот не брать, а вырезать.

Но тогда у меня бы не было наглядности, что конкретно у меня есть в этом Customer Force State.

То есть я бы тогда не понимал конкретно, что у меня там есть.

Потому что при наведении ты этого не видишь.

А когда ты делаешь пиком, в чем мне понравился пик, у тебя код длиннее получается в конкретных ситуациях, но зато ты видишь конкретно, какие поля ты берешь.

Ты понимаешь четко, что ты имеешь на данный момент.

Это очень удобно.

Дальше сделаем заголовок странички.

Это просто Use SEO Meta.

Здесь максимально все просто, я думаю.

Редактирование кампании.

Причем он говорит cannot find name, прикинь, не может найти все мета, ну, мы потом перезагрузим, я думаю, найдет Дальше, а как нам из нашего, видишь, у нас id квадратики, да, идет, id-шник, как нам из него получить тот самый id-шник То есть чтобы мы из этого пути, customers edit, слэш 1, получили тот самый 1, либо 2, либо 3 и так далее Это очень просто, для этого нужен нам роутер, const router use route, ну, точнее, не роутер, а роут, прошу прощения

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

Вот, теперь он знает, что такое роут.

Во Vue это нормально перезагружать, потому что в Vue плохо система его читает, слишком он непонятный.

React как чистый лист, знаешь, а для редактора кода, для обычного Vue это прям какая-то магия.

И дальше мы пишем, из этого роута заберем Customer ID, пишем Customer ID, для удобства опять же, router.params.id, ну и в конце укажем, что это будет стринговый у нас, почему так, потому что если наведем, он будет показывать, что либо стринг, либо стринг в массиве

У нас это является точно string, поэтому я укажу точно string, иначе у нас будет ругаться дальше степизация.

Окей, дальше создадим саму форму.

Мы это с вами уже умеем делать, скопируем просто со сделки.

Вот таким образом, да, мы берем useForm, указываем, что такое customerFormState, забираем поля handleSubmit, defineField и вот эти новые поля, которые вы еще не видели.

Что значит эти поля?

Это поле позволяет нам, это функция, точнее, позволяет нам изменить,

Любое поле, любой стейт, вот этот вот, мы, допустим, можем написать setFieldAvatarURL, меняем на такой-то.

Тем самым мы с помощью функции можем менять наше состояние внутри формы.

setValues то же самое, что это, только меняет не одно состояние, а сразу пачку целую, все поля.

А values это просто получение сразу всех полей отсюда.

То есть просто целиком забираем все value, которые у нас есть в данный момент Теперь сделаем useQuery UseQuery нам позволит получить Смотрите, нам что нужно сделать Когда мы грузим страничку редактирования товара какого-то Ну, либо в нашем случае компании Сначала нам нужно подгрузить старые данные и записать их в нашу форму Чтобы было с чего начинать, да?

А потом мы будем редактировать и перезаписывать Давайте это сделаем с помощью useQuery

const, забираем даты из success, дальше расскажу почему, useQuery, query ключ, getCustomer и customerId непосредственно.

И сама функция следующим образом выглядит, getDocument, dbId, collectionCustomers, customerId.

Опять же, мы это уже с вами делали много раз в этом ролике, я уже рассказывал, как это работает, здесь максимально все просто, все то, что мы уже знаем.

Вот такая простая история, useQuery, да, но вы спросите, Макс, а зачем ты взял из success?

Смотрите, ребят, помните, я говорил в этом ролике, по-моему, ближе к началу, что у нас в новой версии ViewQuery убрали onSuccess.

То есть больше onSuccess нет.

Вот этого onSuccess больше у нас не существует.

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

Сейчас мы это делать не можем.

Поэтому вместо этого мы теперь берем и success,

И с помощью watch, watch это специальная функция внутри Vue.js, которая позволяет отслеживать какую-то перемену, либо ее слушать.

Мы слушаем из success, слушаем ее, так сказать, изменения.

И далее, когда мы слушаем эти изменения, мы производим определенные действия.

В нашем случае мы должны с вами записать все, что мы видим, вот все, что здесь поменяется, все эти данные мы должны записать с вами в наш массив set values.

Вот сюда.

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

А потом мы их можем редактировать.

Как это все происходит?

Все очень просто.

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

То есть мы пишем constant initial data, data value является unknown и iCustomerFormState, потому что мы знаем, что оттуда придет.

Вот.

И дальше с помощью функции setValues мы наполним просто все поля.

Вот таким образом.

То есть мы просто прокинем сюда объект, помните, я говорил, что setValues принимает сразу все value.

И тут прекрасно работают подсказки, потому что мы типизировали все выше, вот здесь вот типизировали, поэтому все работает.

E-mail, Initial Data E-mail, Avatar, Initial Data Avatar URL.

Тут мы пишем или это, или пустое значение, потому что это необязательное поле, его может не быть.

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

И имя вот сюда также заносим.

То есть мы сейчас заносим данные по умолчанию.

Теперь то, что мы уже, опять же, делали, это мы связываем наши input с нашей формой.

Опять же, напомню, что здесь сделано супер неудобно во Vue.

Точнее, не то, что во Vue, а в библиотечке Vvalidate.

Пусть они возьмут пример с ReactHooForm.

Там вообще это идеально сделано.

Через регистр вообще топчик.

Здесь же вам приходится описывать DefineField и каждое поле по отдельности.

Это супер неудобно.

Вот, заметьте, мы не описываем здесь поле аватар URL, мы будем менять его отдельно, в отдельной концепции, когда будем грузить картинку на сервак Поэтому пока что мы этим не занимаемся И сделаем последнюю мутацию, которая будет отвечать у нас за то, чтобы мы обновляли наши данные Как она будет выглядеть?

UseMutation и Spending, то есть когда мы отправили запросы на сервак и ждем, mutate для выполнения мутации Ключ UpdateCustomerId

Функция принимаемся дату, которая будет являться iCustomerForumState.

И здесь мы просто передаем.

Вот сюда в последнюю очередь.

В принципе, все.

В принципе, все.

Дальше нам предстоит еще сделать загрузку картинок.

Но к ней обещал вернуться позже.

И последнее, это эту мутацию нужно вывести в кнопку onSubmit.

Я верну HandleSubmit.

То есть мы, ну не в кнопку, а в форму точнее.

Это вот onSubmit, который будет привязываться к форме.

Когда форма будет сабмиться, отправляться на сервак, будем выполнять HandleSubmit и получать value и их отправлять дальше в mutate.

Все.

Здесь все понятно.

Теперь напишем HTML немножко.

Единственное, что мы забыли сделать, еще и не сделали, да, это касаемо загрузка имиджа.

Мы ее здесь добавим потом, upload image, upload image.

И опять же, ребят, не стесняйтесь делать рефакторинг.

Если вам кажется, что код слишком весом, на самом деле этот компонент, он не идеален.

В предыдущем мы взяли идеально, этот уже такой, знаешь, не супер идеально.

Я бы его тоже немножко отрефакторил опять же.

То есть вынес бы отдельно по кусочкам и так далее, как вы уже умеете делать.

Это идеально будет для вас домашнее задание.

Так, пойдем дальше.

Сначала накинем базовые стили, я их просто копирую.

Вот, это для input, для наших стилей.

Вот, пожалуйста, они готовы.

Итак, для начала мы с вами сделаем padding 10.

Классика жанра.

Можно вообще это в layout вынести, на самом деле, по-хорошему.

Что-то я не подумал.

Ну, окей, это уже сам, если надо, сделайте.

Заголовочек вешаем, редактирование.

Тут будет название нашего, когда мы его подгрузим.

Мы датанейм выводим, то есть название нашего текущего клиента, которого мы будем редактировать.

Далее мы делаем форму, делаем событие с помощью собачки.

Собачка submit, он submit, что будет происходить при сабмите формы.

Далее мы с вами делаем поля.

Опять же, мы их уже связывали миллионы раз, поэтому просто их вставляем.

Вот email, вот наименование, вот откуда пришел.

Все, базовые поля.

И последнее, что нам стоит сделать, кнопку.

Кнопка будет выключена, когда идет запрос на сервак, чтобы мы два раза не кликнули случайно.

И тоже будет состояние, либо загрузка идет, либо сохранение.

Все.

То есть либо кнопка «Сохранить» и осталось нам сделать load image and preview.

Сохраним, посмотрим, что у нас получилось.

Сейчас на данный момент, по идее, должна произойти подгрузка данных, заполнение всех полей, а потом уже мы сделаем еще загрузку картинок.

Обновляемся.

Как видишь, все произошло успешно.

То есть у нас заполнился name, email и откуда пришел, у нас нету этого значения.

Давайте вернемся назад.

Альфа-банк перейдем.

Здесь есть все данные.

И вот это название, и откуда пришел, и все это присутствует.

Окей, идем назад.

Откроем Sportmaster, у меня просто под него логотип идеальный есть, поэтому откроем именно его.

Окей, а дальше мы, собственно, с вами сделаем загрузку картинок.

Загрузку картинок, upload image, давайте, собственно, тоже этим аккуратненько займемся.

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

В том числе типизация ивентов различных.

То есть в React ты мог написать там change event, там в React очень много типов для описания ивентов.

Что значит описание ивента?

Нам нужно сейчас что сделать?

Сделать поле для загрузки картинок, из него забрать event target files и конкретный файл отправить на сервак загрузить.

Так вот, если мы это будем реализовывать через условно обычный...

Ну, без типизации, то будет ужасно Там будет подсвечиваться ошибка и так далее Поэтому нам приходится руками дописывать такую штуку Что у нас есть input файл event Он экстендится от обычного event И здесь target является стимул input элемента Вот, и то, это нам не до конца не поможет Но хоть что-то, хоть что-то, хоть какая-то типизация Дальше я покажу, где мы его будем использовать

Теперь все очень просто.

Нам осталось сделать загрузку данных на сервак.

Она будет происходить через мутацию, как и все в этом проекте, в принципе.

Ну, как и в любом проекте через ViewQuery.

Поэтому делаем мутацию.

Итак, делаем UseMutation.

Забираем некоторые поля отсюда.

Нам потребуется Mutate.

Я его переименую специально в UploadImage, потому что Mutate уже у нас был, чтобы не было конфликтов.

И из Painting также переименую в UploadImagePainting, потому что это опять же у нас уже было.

Дальше ключ будет UploadImage.

Сама функция будет следующего характера, мы берем файл, именно тип файл, это файл, это тип дает нам сам JavaScript, базовый тип Который нам отдает как раз таки сама наша форма, ну форма именно поле, поле с input type файл Берем storage, create file, передаем сюда storage id обязательно Берем сюда обязательно ui id, передаем, к сожалению оно у нас удалилось, давайте сделаем заново, импорт

Import UID.

Давайте посмотрим.

From UID.

Так, здесь UID какой есть?

Никакого нет, да?

UID, к сожалению, нас просто развернули.

Давай еще раз Extension Host перезагрузим.

Вот был бы файл с расширением TSX, просто была бы конфетка.

Но здесь, к сожалению, имеем то, что имеем.

Подожди, а что у нас вообще ничего не работает?

Стой, подожди.

А, там по-другому, да?

А, там V4.

Ладно, мой trouble, мой trouble.

Вот так сделаем.

Все сохраним.

Так.

Теперь возьмем эту историю сюда и вставим просто.

Хорошо.

Это уникальный ID-шник просто.

Ну и файл передаем в качестве body.

Далее.

Нам нужно...

Так, опустимся ниже.

При успешном запросе нужно сделать определенное действие.

Берем, пишем, пишем onSuccess, получаем дату.

И дальше эту дату, которую мы получили, мы должны... То есть смотрите, какая методология, да?

Мы грузим на сервак файлик.

Этот сервак нам отвечает ответом успешным.

У нас готова ссылка на весь файл.

Эту ссылку мы потом записываем в нашу базу данных к нашему клиенту непосредственно.

Поэтому здесь мы в респонсе...

С помощью специальной функции, которую нам предоставляет сам же app-write, берем storage, get-file-download, получаем прямую ссылку с помощью передачи сюда storage-id и самого id-шника, который получаем здесь.

Это id файла, который был загружен.

Получаем ссылку конкретную и эту ссылку записываем вот в нашу форму.

С помощью set-field-value записываем по этому полю такую-то ссылку.

Все.

Идеально.

И осталось это все имплементировать в наш HTML-код.

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

Сделаем обычный тег image.

Я не буду делать next image, потому что он кэширует, и, возможно, будет как проблема в next.

Из-за того, что он кэширует, потом будут проблемы при апдейте.

А у нас он будет часто обновляться, потому что мы хотим загрузить, получить новые, обновить, и, возможно, будут какие-то траблы определенные.

Так что я во избежание ошибок сделал обычный тег image.

Он, в принципе, здесь оптимизация нам не важна.

Берем, если у нас при определенном условии, мы его показываем.

Если у нас есть аватар URL и если у нас идет загрузка.

Вот, только тогда мы его показываем.

Значит, дальше.

Сама ссылка будет values avatar URL.

Мы просто заберем value из нашей формы, ее здесь просто выводим.

И дальше, собственно, идем дальше.

Делаем сам input.

Сам input оборачиваем в div, в grid, чтобы красивенько все смотрелось.

Label.

Пишем логотип и сам UI-инпут используем обычный от нашей ShadCN, который мы делали до этого.

Только указываем TypeFile обязательно.

Вот что я вам и говорил, да, мы используем OnChange, то есть при изменении, то есть при загрузке картинки, мы должны Event обязательно пометить тем, что мы уже описали вот этим вот таргетом.

И только в таком случае он нам даст спокойно писать EventTargetFilesLength.

Вот, то есть у нас в каждом Event есть TargetFiles, да, особенно если у нас TypeFile является.

Дальше мы проверяем, если у нас есть хоть один файл загружен через эту штуку, то мы можем выполнять загрузку данных на сервере с помощью выполнения функции uploadImage, которую мы описали выше, вот здесь Мы ее выполняем, тем самым файлик передаем вот сюда, потом передаем сюда и все, все успешно создается Конфетка, я считаю, что да И последнее, что нам осталось сделать, это disable, то есть кнопка будет недоступна при загрузке, когда идет загрузка, кнопка недоступна

Вот, собственно, и все.

Сохраняем, ребята.

Давай тестировать будем.

Какой у нас такой плотный ролик получился.

Прям очень плотненький.

Прям очень-очень плотненький.

Так.

Блин, конечно, паддингов у меня очень не хватает.

Паддингов здесь.

Так, а у меня импуты что, не назначены по классам?

Где здесь, да?

Где-то есть, где-то нету, да?

Так, давайте посмотрим еще раз сюда.

Я бы... У картинки я бы сделал точно... Маржин Y у нее есть, кстати говоря, да?

Я бы... Вот самим импутом... Подожди.

Margin bottom где-то по 4 я бы сделал даже, да?

А здесь я бы сделал тоже по 4.

Вот что-нибудь такое.

Чтобы, да, просто у них был больше space.

В принципе, все устроено.

Откроем inspect.

Обновимся еще раз и смотрим нашу консоль.

Все, у нас все загрузилось.

Давайте добавим, откуда пришел.

Пришел он к нам с Instagram.

Instagram.

Запрещен составить в Российской Федерации.

Не знаю, надо это говорить или нет.

Так, логотипчик сюда нажимаем и грузим сюда Nike.

Open.

Ждем какое-то время.

Загрузка проходит.

Логотип загружен.

Идеально просто.

Можем посмотреть Network, как это все происходило у нас.

Вот, пожалуйста, есть.

За мной вы нифига не увидите.

Вот есть файлик.

Да, вот файл.

Вот такой пилот мы передаем.

Передаем файл и файл ID.

Это наш binary код.

Дальше вот по такому запросу идет превью Вот такое получаем превьюшку в ответе И, как видите, вот респонс полноценно мы видим То есть кто что может делать, его полностью, ссылку и так далее Отлично Все Теперь, как проверить, перезаписались данные или нет Просто обновляем страничку Если мы увидим тот же самый логотип, значит, что перезаписалось А, мы забыли сохранить, да, ребят, по-моему?

Я забыл, по-моему, сохранить Давай еще раз загрузим Забыл сохранить просто Так

Так, нажимаем «Сохранить» и ожидаем.

Загрузка идет, видишь, все, загрузка успешно произведена.

Посмотрим сюда.

Ну, можно обновить, в принципе, просто еще раз.

Да, видишь, все, все, то есть конфетка у нас полностью работает, идеально.

Можно прийти на Home, посмотреть, что у нас вот здесь все также осталось идеально.

Разработка сайта, там настроить компанию, все идеально работает.

Фух, вот такой у нас проектик, ребят, получился.

Сейчас я посмотрю, ничего мы не забыли случайно.

Не, наверное, ребят, мы ничего не забыли.

Наверное, мы ничего не забыли, но все работает идеально.

Фух, давайте в конце, ребят, отпразднуем это событие.

Вы ставьте в комментариях, кто досмотрел до этого момента, ставьте в комментариях эмоджи вот этих вот фейерверков, конфетти и так далее.

А я установил себе крутой экстеншн на стриме недавно, и мы сейчас с вами вместе отпразднуем завершение нашего проекта.

Очередной большой проект на канале.

В конце сейчас поделюсь своими мнениями о Vue.js, о Nuxity, какие минусы я нашел, какие плюсы я нашел, и об AppWrite, и о всем том, что я использую новенько в этом проекте.

Давайте, во-первых, вместе отпразднуем.

Запускаем просто конфетти.

Как же это приятно.

Столько конфетти запустил, что чуть-чуть экране залагал.

На самом деле, они нормально оптимизированы, работают.

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

Всем, кто сделал вместе со мной этот проект, ребят, все вы просто красавчики, все вы просто лучшие.

Так что все, проект готов.

Ставьте конфетти в комментариях.

Мы с вами его отразили.

Прям приятное ощущение на самом деле.

Блин, какой же тяжелый был ролик.

Но надеюсь, вам понравилось реально.

Надеюсь, вам реально понравилось.

Сейчас переходим к итогам того, что мы сделали.

И, собственно, к...

Моему мнению по Vue, по Nux и так далее.

И не забываем, ребят, проект можно скачать абсолютно бесплатно.

Все для вас, ребят.

Ссылка в описании.

Переходите, скачивайте, кайфуйте и так далее.

Проект, опять же, повторюсь, мы обычно раздаем проект только на платной основе, но здесь решили пойти к вам навстречу.

И хочу поменять модель, хочу давать больше пользы.

Надеюсь, у меня это получилось сделать.

Опять же, ваши лайки, это будет лучшая поддержка, и комментарии, и подписки, самое важное, на канал.

Вот.

А мы сделаем небольшой итог.

Значит, смотрите.

Небольшой итог того, что нам нужно сделать.

Смотрите.

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

Итак, что в итоге мы имеем по Vue.js и по Naxx?

Что я могу сказать?

Смотрите, мой итог по Vue.js, это вот то, что мы сейчас сделали на видео, и в том числе то, что я делал отдельно за кадром, когда разрабатывал этот проект.

Значит, смотрите, у нас минус первый, мало крутых библиотек.

Этот минус исходит из-за того, что маленькая комьюнити.

Маленькая комьюнити, потому что это не самый популярный фреймворк.

Да, у нас React на первом месте все-таки идет, Vue на втором, на мой взгляд.

Поэтому мало фреймворков, точнее, мало библиотек и мало также вопросов, ответов на Stack Overflow и других платформах.

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

На Vue бывают проблемы, которые решения ты не найдешь в интернете, потому что с ними просто еще никто не сталкивался.

Вот такой момент тоже бывает.

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

И это усложняет, почему я про это говорю, это усложняет обучение новичку.

Вот эту плавность обучения новичку, она реально усложняет.

Когда новичок сталкивается с проблемой, он даже не может в интернете посмотреть, как ее решать.

И это, конечно, большая проблема.

Дальше, минус, то, что до сих пор, еще я не знаю, в каком году и когда вышла в U3,

Но до сих пор еще многие библиотеки поддерживают только Vue 2, а Vue 2, ну это ужас, по сравнению с Vue 3, Vue 3 это прям космос, Vue 2 это прям ужас полнейший, то есть я никому не рекомендую с ним работать, честно, я когда помню на Vue 2 писал, я больше не хотел на него возвращаться, это прям олдскульная история такая, которая никому и врагу не пожелаешь, да, даже реакторские компоненты на классах были и то лучше.

Поэтому печально, что еще некоторые остаются на Vue 2 и из-за этого нету даже TypeScript.

Это тоже печально и не так комфортно разрабатывать непосредственно.

Следующий минус, который вы видели все сами в этом уроке, TypeScript постоянно вылетает.

Именно TypeScript сервер, который отвечает за типизацию в ваших проектах, он постоянно вылетает, потому что среда разработки не понимает, что вы используете Vue.js.

У вас расширение файла не TypeScript, у вас .vue.js.

И из-за этого огромная проблема с типизацией.

То есть она постоянно вылетает, он не понимает, где есть типизация, где ее нет.

И иногда даже не подсвечивает вам ошибки.

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

Он просто даже ошибку не пишет.

Это печально, это реально печально.

Потом, нет подсказки импортов.

То есть такая мелочь, но тоже приятно.

То есть в React у вас всегда импорты подсказываются, особенно с TypeScript, они работают идеально.

Всегда подсказки и автокомплит и так далее.

Во Vue такого нету.

Ну, есть, точнее, крайне редко, крайне редко.

Потом, большое количество магии под капотом.

Новичкам это в плюс, то есть вы, когда заходите во Vue.js, вам кажется все таким легким по сравнению с React, там все понятным, и обычный HTML вместо JSX, и там, ну, куча других нюансов, вы прям кайфуете и наслаждаетесь.

Но как только вы начинаете писать что-то более сложное, вот то, что мы сегодня писали, да, либо проект еще сложнее...

вы понимаете, что на самом деле у вас полные, большие проблемы, скажем так, да, а проблемы с тем, что много происходит магии под капотом, это круто для новичка, но плохо для профессионала, потому что профессионал пишет крутые масштабируемые приложения, которые важно описывать тестами, как писать тесты во Vue.js я пока не понимаю, потому что ты не понимаешь, как работает все под капотом, вот это прям огромный для меня вопрос.

Вот, ну и последний такой минус небольшой тоже заметил, что ужасная поддержка вискода, в AppStorm он работает идеально, в UGS, в вискоде он работает печально, то есть некоторые HTML атрибуты даже не подсвечиваются, как вы видели, опять же, приходится делать рестарт эксенсионов, чтобы они заработали, но бывают такие моменты, когда даже HTML атрибуты у тебя не подсвечиваются.

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

Касаемо этого, у меня был шорт за канал, недавно выходил сравнение React и Vue, можете посмотреть.

Вот, касаемо дальше другой, АПП в райте.

Это то, где мы писали бэкэнд, что я могу сказать.

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

Штука невероятно крутая, гораздо лучше, чем в страпе, гораздо лучше, чем в супа бейс.

Разработчики красавчики, они учли все минусы вот тех готовых систем и сделали что-то поистине крутое.

Оно, я не знаю, какие-то есть минусы или нет, я даже не знаю, пока я их не увидел.

Пока я часто даже не увидел.

Оно все в пределах разумных, в пределах того, что надо.

Ну, то есть, это то, что реально вот нужно нашему рынку.

Касаемо Strapi, это ужасная история, поэтому я о ней уже говорил в прошлом ролике на канале в большом проекте.

Короче, из таких изготовок, а выбрать на первом месте, на втором, наверное, Supabase, потом, наверное, Strapi, вот так я бы так сказал.

Касаемо еще ShadCN, очень понравилась тема, это когда мы компоненты с вами генерировали с помощью команды.

Очень крутая тема, реально понравилась.

Вот 100% всем рекомендую тоже попользоваться, потому что это как бы вроде бы UI-библиотека, она реально ускоряет разработку, но при этом вы сами меняете их стили, и это расположено прямо внутри вашего проекта.

Штука прям крутая, рад, что подписчики мне ее посоветовали.

Наверное, в принципе, это все.

Да, ребят, напоминаю, 2000 лайков набираем, снимаем следующий большой проект.

Все полезные ссылки будут в описании.

На этом все, с вами был Макс, ребят.

Всем пока.

Не забывайте нажать подписку, на телегу подписываться и так далее.

И пишите в комментариях вообще, понравился вам или нет такой формат.

Всем пока.