{"id":385548,"date":"2024-06-29T06:10:30","date_gmt":"2024-06-29T06:10:30","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=385548"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=385548","title":{"rendered":"<span>\u0420\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u043e \u043f\u043e Supabase. \u0427\u0430\u0441\u0442\u044c 2<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/webt\/pw\/hr\/ps\/pwhrpsl3_b_b80p3rzkdgc5yu_c.jpeg\" data-src=\"https:\/\/habrastorage.org\/webt\/pw\/hr\/ps\/pwhrpsl3_b_b80p3rzkdgc5yu_c.jpeg\" data-blurred=\"true\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041f\u0440\u0438\u0432\u0435\u0442, \u0434\u0440\u0443\u0437\u044c\u044f!<\/p>\n<p>  <\/p>\n<p>\u0412 \u044d\u0442\u043e\u043c \u0446\u0438\u043a\u043b\u0435 \u0438\u0437 2 \u0441\u0442\u0430\u0442\u0435\u0439 \u044f \u0445\u043e\u0447\u0443 \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u0442\u044c \u0432\u0430\u043c \u043e <a href=\"https:\/\/supabase.com\/\">Supabase<\/a> \u2014 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u0439 (open source), \u0442.\u0435. \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u043e\u0439 \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0435 <a href=\"https:\/\/firebase.google.com\/\">Firebase<\/a>. \u0412 <a href=\"https:\/\/habr.com\/ru\/company\/timeweb\/blog\/648761\/\">\u043f\u0435\u0440\u0432\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435<\/a> \u043c\u044b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043b\u0438 \u0442\u0435\u043e\u0440\u0438\u044e, \u0432 \u044d\u0442\u043e\u0439 \u2014 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u043e\u0435 <code>social app<\/code> \u0441 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0435\u0439, \u0431\u0430\u0437\u043e\u0439 \u0434\u0430\u043d\u043d\u044b\u0445, \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435\u043c \u0444\u0430\u0439\u043b\u043e\u0432 \u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u043e\u0439 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438.<\/p>\n<p>  <\/p>\n<p><a href=\"https:\/\/github.com\/harryheman\/Blog-Posts\/tree\/master\/supabase-social-app\">\u0420\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0441 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u043c \u043a\u043e\u0434\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432\u0430\u043c \u044d\u0442\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e, \u043f\u0440\u043e\u0448\u0443 \u043f\u043e\u0434 \u043a\u0430\u0442.<\/p>\n<p><a name=\"habracut\"><\/a>  <\/p>\n<h2 id=\"chto-takoe-supabase\">\u0427\u0442\u043e \u0442\u0430\u043a\u043e\u0435 Supabase?<\/h2>\n<p>  <\/p>\n<p><code>Supabase<\/code>, \u043a\u0430\u043a \u0438 <code>Firebase<\/code> \u2014 \u044d\u0442\u043e <code>SaaS<\/code> (software as a service \u2014 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u043e\u0435 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u0435 \u043a\u0430\u043a \u0443\u0441\u043b\u0443\u0433\u0430) \u0438\u043b\u0438 <code>BaaS<\/code> (backend as a service \u2014 \u0431\u044d\u043a\u0435\u043d\u0434 \u043a\u0430\u043a \u0443\u0441\u043b\u0443\u0433\u0430). \u0427\u0442\u043e \u044d\u0442\u043e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442? \u042d\u0442\u043e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u0441 <code>fullstack app<\/code> \u043c\u044b \u0440\u0430\u0437\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0443\u044e \u0447\u0430\u0441\u0442\u044c, \u0430 \u0432\u0441\u0435 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f <code>Supabase<\/code> \u0447\u0435\u0440\u0435\u0437 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0435 \u043a\u043e\u043c\u043f\u043b\u0435\u043a\u0442\u044b \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u043e\u0433\u043e \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u044f (<code>SDK<\/code>) \u0438 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u044b \u043f\u0440\u0438\u043a\u043b\u0430\u0434\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f (<code>API<\/code>). \u041f\u043e\u0434 &#171;\u0432\u0441\u0435\u043c \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u043c&#187; \u043f\u043e\u0434\u0440\u0430\u0437\u0443\u043c\u0435\u0432\u0430\u0435\u0442\u0441\u044f \u0441\u0435\u0440\u0432\u0438\u0441 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 (\u0432\u043a\u043b\u044e\u0447\u0430\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0445 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u043e\u0432), \u0431\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 (<a href=\"https:\/\/www.postgresql.org\/\">PostgreSQL<\/a>), \u0444\u0430\u0439\u043b\u043e\u0432\u043e\u0435 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435, realtime (\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438) \u0438 \u0441\u0435\u0440\u0432\u0435\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u0441\u0435 \u044d\u0442\u043e \u043e\u0431\u0441\u043b\u0443\u0436\u0438\u0432\u0430\u0435\u0442.<\/p>\n<p>  <\/p>\n<h2 id=\"podgotovka-i-nastroyka-proekta\">\u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/h2>\n<p>  <\/p>\n<p><em>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435<\/em>: \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u044f \u0431\u0443\u0434\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <a href=\"https:\/\/ru.reactjs.org\/\">React<\/a>, \u043d\u043e \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u0432\u043e\u0439 \u043b\u044e\u0431\u0438\u043c\u044b\u0439 <code>JS-\u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a<\/code> \u2014 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0439 \u0441 <code>Supabase<\/code>, \u0431\u0443\u0434\u0435\u0442 \u0447\u0442\u043e \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f <code>framework agnostic<\/code>. \u0422\u0430\u043a\u0436\u0435 <em>\u043e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435<\/em>, \u0447\u0442\u043e &#171;\u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u043e\u0435 <code>social app<\/code>&#187; \u043d\u0435 \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043d\u043e\u0435 \u043d\u0430\u043c\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 <code>production ready<\/code>, \u043e\u0434\u043d\u0430\u043a\u043e, \u043f\u043e-\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438, \u044f \u043f\u043e\u0441\u0442\u0430\u0440\u0430\u044e\u0441\u044c \u0430\u043a\u0446\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u0430\u0448\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0445 \u0434\u043e\u0440\u0430\u0431\u043e\u0442\u043a\u0430\u0445.<\/p>\n<p>  <\/p>\n<p>\u0412\u044b \u0433\u043e\u0442\u043e\u0432\u044b? \u0422\u043e\u0433\u0434\u0430 \u0432\u043f\u0435\u0440\u0435\u0434!<\/p>\n<p>  <\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0448\u0430\u0431\u043b\u043e\u043d \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <a href=\"https:\/\/vitejs.dev\/\">Vite<\/a>:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\"># supabase-social-app - \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f # --template react - \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0439 \u0448\u0430\u0431\u043b\u043e\u043d yarn create vite supabase-social-app --template react<\/code><\/pre>\n<p>  <\/p>\n<p>\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c\u0441\u044f \u0438\u043b\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0435\u043c\u0441\u044f \u043d\u0430 <a href=\"https:\/\/supabase.com\/\">supabase.com<\/a> \u0438 \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u044b\u0439 \u043f\u0440\u043e\u0435\u043a\u0442:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/vy\/nq\/sr\/vynqsroie4_pfj5uafccto74u1m.png\" data-src=\"https:\/\/habrastorage.org\/webt\/vy\/nq\/sr\/vynqsroie4_pfj5uafccto74u1m.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/j9\/n-\/sk\/j9n-sk5svcil9pydtehwwf83hcu.png\" data-src=\"https:\/\/habrastorage.org\/webt\/j9\/n-\/sk\/j9n-sk5svcil9pydtehwwf83hcu.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041a\u043e\u043f\u0438\u0440\u0443\u0435\u043c \u043a\u043b\u044e\u0447 \u0438 <code>URL<\/code> \u043d\u0430 \u0433\u043b\u0430\u0432\u043d\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u043f\u0430\u043d\u0435\u043b\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u043c:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/ce\/e8\/ig\/cee8ig38ny_za1mfgyedf061apm.png\" data-src=\"https:\/\/habrastorage.org\/webt\/ce\/e8\/ig\/cee8ig38ny_za1mfgyedf061apm.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u0417\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0438\u0445 \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0441\u0440\u0435\u0434\u044b \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0432 \u043a\u043e\u0440\u043d\u0435\u0432\u043e\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 (<code>supabase-social-app<\/code>) \u0444\u0430\u0439\u043b <code>.env<\/code> \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">VITE_SUPABASE_URL=https:\/\/your-url.supabase.co VITE_SUPABASE_KEY=your_key<\/code><\/pre>\n<p>  <\/p>\n<p><em>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435<\/em>: \u043f\u0440\u0435\u0444\u0438\u043a\u0441 <code>VITE_<\/code> \u0432 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u043c.<\/p>\n<p>  <\/p>\n<p>\u041d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 <code>Authentication<\/code> \u043f\u0430\u043d\u0435\u043b\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 <code>Settings<\/code> \u043e\u0442\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u044c \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u0430\u0434\u0440\u0435\u0441\u0430 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b \u043d\u043e\u0432\u044b\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c (<code>Enable email confirmation<\/code>):<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/4s\/a2\/sv\/4sa2svpaiz51tai2hkxrucpw9vo.png\" data-src=\"https:\/\/habrastorage.org\/webt\/4s\/a2\/sv\/4sa2svpaiz51tai2hkxrucpw9vo.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p><em>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435<\/em>: \u043f\u0440\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043c\u044b \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u043c \u0448\u0430\u0433 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c\u0438 \u0441\u0432\u043e\u0435\u0433\u043e <code>email<\/code> \u043f\u043e\u0441\u043b\u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0432 \u0446\u0435\u043b\u044f\u0445 \u044d\u043a\u043e\u043d\u043e\u043c\u0438\u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u0438. \u0412 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u0432 \u043e\u0431\u044a\u0435\u043a\u0442\u0435 <code>user<\/code> \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c\u0441\u044f \u043f\u043e\u043b\u0435 <code>isEmailConfirmed<\/code>, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u2014 \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u0442\u043e\u0433\u043e, \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u043b \u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0441\u0432\u043e\u0439 <code>email<\/code>. \u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044f \u0431\u0443\u0434\u0435\u0442 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c \u043b\u043e\u0433\u0438\u043a\u0443 \u0440\u0430\u0431\u043e\u0442\u044b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432 \u0447\u0430\u0441\u0442\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.<\/p>\n<p>  <\/p>\n<p>\u0412 \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0430\u043c \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f 3 \u0442\u0430\u0431\u043b\u0438\u0446\u044b:<\/p>\n<p>  <\/p>\n<ul>\n<li><code>users<\/code> \u2014 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438;<\/li>\n<li><code>posts<\/code> \u2014 \u043f\u043e\u0441\u0442\u044b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439;<\/li>\n<li><code>comments<\/code> \u2014 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \u043a \u043f\u043e\u0441\u0442\u0430\u043c.<\/li>\n<\/ul>\n<p>  <\/p>\n<p><code>Supabase<\/code> \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u043c\u0438 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 <code>Table Editor<\/code>:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/vt\/sv\/01\/vtsv01him07-j_t8opbcf0uthsy.png\" data-src=\"https:\/\/habrastorage.org\/webt\/vt\/sv\/01\/vtsv01him07-j_t8opbcf0uthsy.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041d\u043e \u043c\u044b \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u043e\u043c <a href=\"https:\/\/ru.wikipedia.org\/wiki\/SQL\">SQL<\/a> \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 <code>SQL Editor<\/code> (\u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043d\u0435 \u0438\u0449\u0435\u043c \u043b\u0435\u0433\u043a\u0438\u0445 \u043f\u0443\u0442\u0435\u0439)). \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 (<code>New query<\/code>) \u0438 \u0432\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u0442\u0430\u043a\u043e\u0439 <code>SQL<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"sql\">CREATE TABLE users (   id text PRIMARY KEY NOT NULL,   email text NOT NULL,   user_name text NOT NULL,   first_name text,   last_name text,   age int,   avatar_url text,   created_at timestamp DEFAULT now() );  CREATE TABLE posts (   id serial PRIMARY KEY,   title text NOT NULL,   content text NOT NULL,   user_id text NOT NULL,   created_at timestamp DEFAULT now() );  CREATE TABLE comments (   id serial PRIMARY KEY,   content text NOT NULL,   user_id text NOT NULL,   post_id int NOT NULL,   created_at timestamp DEFAULT now() );<\/code><\/pre>\n<p>  <\/p>\n<p><em>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435<\/em>: \u043c\u044b \u043d\u0435 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u043d\u0435\u0448\u043d\u0438\u0435 \u043a\u043b\u044e\u0447\u0438 (<code>FOREIGN KEY<\/code>) \u0432 \u043f\u043e\u043b\u044f\u0445 <code>user_id<\/code> \u0438 <code>post_id<\/code>, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u044d\u0442\u043e \u0443\u0441\u043b\u043e\u0436\u043d\u0438\u0442 \u0440\u0430\u0431\u043e\u0442\u0443 \u0441 <code>Supabase<\/code> \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435 \u0438 \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u043b\u043e\u0433\u0438\u043a\u0438, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u043e\u0439 \u0441 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0438 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435\u043c \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0442\u0430\u0431\u043b\u0438\u0446 (<code>ON UPDATE<\/code> \u0438 <code>ON DELETE<\/code>).<\/p>\n<p>  <\/p>\n<p>\u041d\u0430\u0436\u0438\u043c\u0430\u0435\u043c \u043d\u0430 \u043a\u043d\u043e\u043f\u043a\u0443 <code>Run<\/code>:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/e1\/pr\/hg\/e1prhg0cepvhxpbzttfqb9nkpd0.png\" data-src=\"https:\/\/habrastorage.org\/webt\/e1\/pr\/hg\/e1prhg0cepvhxpbzttfqb9nkpd0.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041c\u044b \u043c\u043e\u0436\u0435\u043c \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0430\u043c\u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445 <code>Table Editor<\/code> \u0438 <code>Database<\/code> \u043f\u0430\u043d\u0435\u043b\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/hh\/lf\/az\/hhlfaz0juvuueylxwkpw70ldylq.png\" data-src=\"https:\/\/habrastorage.org\/webt\/hh\/lf\/az\/hhlfaz0juvuueylxwkpw70ldylq.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/pi\/7a\/ye\/pi7ayenyuqulz8jy_hy2lqtado8.png\" data-src=\"https:\/\/habrastorage.org\/webt\/pi\/7a\/ye\/pi7ayenyuqulz8jy_hy2lqtado8.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p><em>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435<\/em> \u043d\u0430 \u043f\u0440\u0435\u0434\u0443\u043f\u0440\u0435\u0436\u0434\u0435\u043d\u0438\u0435 <code>RLS not enabled<\/code> \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 <code>Table Editor<\/code>. \u0414\u043b\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u043c \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c \u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0438 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438 \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u0441\u0442\u0440\u043e\u043a\/\u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0438 \u0437\u0430\u0449\u0438\u0442\u044b \u0441\u0442\u0440\u043e\u043a (<a href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/14\/ddl-rowsecurity\">Row Level Security<\/a>). \u0414\u043b\u044f \u0442\u0430\u0431\u043b\u0438\u0446 \u043c\u044b \u044d\u0442\u043e\u0433\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0435 \u0431\u0443\u0434\u0435\u043c, \u043d\u043e \u043d\u0430\u043c \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0431\u0443\u0434\u0443\u0442 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0430\u0432\u0430\u0442\u0430\u0440\u044b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439.<\/p>\n<p>  <\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u044b\u0439 &#171;\u0431\u0430\u043a\u0435\u0442&#187; \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 <code>Storage<\/code> \u043f\u0430\u043d\u0435\u043b\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f (<code>Create new bucket<\/code>):<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/rb\/wx\/dh\/rbwxdhlc-ne6uycyhm2voc2kfke.png\" data-src=\"https:\/\/habrastorage.org\/webt\/rb\/wx\/dh\/rbwxdhlc-ne6uycyhm2voc2kfke.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u0414\u0435\u043b\u0430\u0435\u043c \u0435\u0433\u043e \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u043c (<code>Make public<\/code>):<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/fn\/sd\/rv\/fnsdrvpmmmqtrd8uc09ovcwsbgw.png\" data-src=\"https:\/\/habrastorage.org\/webt\/fn\/sd\/rv\/fnsdrvpmmmqtrd8uc09ovcwsbgw.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u0412 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 <code>Policies<\/code> \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u0443\u044e \u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0443 (<code>New policy<\/code>). \u0412\u044b\u0431\u0438\u0440\u0430\u0435\u043c \u0448\u0430\u0431\u043b\u043e\u043d <code>Give users access to a folder only to authenticated users<\/code> (\u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u0446\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439) \u2014 <code>Use this template<\/code>:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/xv\/rb\/hp\/xvrbhpcrhrlysu9rt2kywcpoyr0.png\" data-src=\"https:\/\/habrastorage.org\/webt\/xv\/rb\/hp\/xvrbhpcrhrlysu9rt2kywcpoyr0.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u0412\u044b\u0431\u0438\u0440\u0430\u0435\u043c <code>SELECT<\/code>, <code>INSERT<\/code> \u0438 <code>UPDATE<\/code> \u0438 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u043c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0438:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/fp\/mc\/js\/fpmcjsj4bc5tksmnro6zwq31kiw.png\" data-src=\"https:\/\/habrastorage.org\/webt\/fp\/mc\/js\/fpmcjsj4bc5tksmnro6zwq31kiw.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041d\u0430\u0436\u0438\u043c\u0430\u0435\u043c <code>Review<\/code> \u0438 \u0437\u0430\u0442\u0435\u043c <code>Create policy<\/code>.<\/p>\n<p>  <\/p>\n<p>\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0435, \u0447\u0442\u043e \u043d\u0430\u043c \u043e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u2014 \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u0434\u0430\u043d\u043d\u044b\u0445 \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0442\u0430\u0431\u043b\u0438\u0446.<\/p>\n<p>  <\/p>\n<p>\u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e <code>Supabase<\/code> \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u044e (<code>publication<\/code>) <code>supabase_realtime<\/code>. \u041d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432 \u043d\u0435\u0435 \u043d\u0430\u0448\u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u044b. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u0432 \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440 <code>SQL<\/code> \u0438 \u0432\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u0442\u0430\u043a\u0443\u044e \u0441\u0442\u0440\u043e\u043a\u0443:<\/p>\n<p>  <\/p>\n<pre><code class=\"sql\">alter publication supabase_realtime add table users, posts, comments;<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0430\u0436\u0438\u043c\u0430\u0435\u043c <code>RUN<\/code>.<\/p>\n<p>  <\/p>\n<p>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u0434\u043b\u044f \u043a\u043b\u0438\u0435\u043d\u0442\u0430:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\"># \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 yarn add @supabase\/supabase-js dotenv react-icons react-router-dom zustand  # \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 yarn add -D sass<\/code><\/pre>\n<p>  <\/p>\n<ul>\n<li><a href=\"https:\/\/www.npmjs.com\/package\/@supabase\/supabase-js\">@supabase\/supabase-js<\/a> \u2014 <code>SDK<\/code> \u0434\u043b\u044f \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 <code>Supabase<\/code>;<\/li>\n<li><a href=\"https:\/\/www.npmjs.com\/package\/dotenv\">dotenv<\/a> \u2014 \u0443\u0442\u0438\u043b\u0438\u0442\u0430 \u0434\u043b\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u043c \u0441\u0440\u0435\u0434\u044b \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f;<\/li>\n<li><a href=\"https:\/\/react-icons.github.io\/react-icons\/\">react-icons<\/a> \u2014 \u0431\u043e\u043b\u044c\u0448\u0430\u044f \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u044f \u0438\u043a\u043e\u043d\u043e\u043a \u0432 \u0432\u0438\u0434\u0435 <code>React-\u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432<\/code>;<\/li>\n<li><a href=\"https:\/\/reactrouter.com\/docs\/en\/v6\/getting-started\/overview\">react-router-dom<\/a> \u2014 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u043b\u044f \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0446\u0438\u0438 \u0432 <code>React-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u0445<\/code>;<\/li>\n<li><a href=\"https:\/\/github.com\/pmndrs\/zustand\">zustand<\/a> \u2014 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\u043c <code>React-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439<\/code>;<\/li>\n<li><a href=\"https:\/\/sass-scss.ru\/\">sass<\/a> \u2014 <code>CSS-\u043f\u0440\u0435\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440<\/code>.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>\u041d\u0430 \u044d\u0442\u043e\u043c \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u044b. \u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043a \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u0430.<\/p>\n<p>  <\/p>\n<h1 id=\"klient\">\u041a\u043b\u0438\u0435\u043d\u0442<\/h1>\n<p>  <\/p>\n<p>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 <code>src<\/code> \u0431\u0443\u0434\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">- api - `API` \u0434\u043b\u044f \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 `Supabase`   - comment.js   - db.js   - post.js   - user.js - components - \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b   - AvatarUploader.jsx - \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0430\u0432\u0430\u0442\u0430\u0440\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f   - CommentList.jsx - \u0441\u043f\u0438\u0441\u043e\u043a \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432   - Error.jsx - \u043e\u0448\u0438\u0431\u043a\u0430 (\u043d\u0435 \u043d\u0430\u0434\u043e \u0442\u0430\u043a \u0434\u0435\u043b\u0430\u0442\u044c \u0432 \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u043d\u0435))   - Field.jsx - \u043f\u043e\u043b\u0435 \u0444\u043e\u0440\u043c\u044b   - Form.jsx - \u0444\u043e\u0440\u043c\u0430   - Layout.jsx - \u043c\u0430\u043a\u0435\u0442 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b   - Loader.jsx - \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438   - Nav.jsx - \u043f\u0430\u043d\u0435\u043b\u044c \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0438   - PostList.jsx - \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u0441\u0442\u043e\u0432   - PostTabs.jsx - \u0432\u043a\u043b\u0430\u0434\u043a\u0438 \u043f\u043e\u0441\u0442\u043e\u0432   - Protected.jsx - \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u043d\u0430\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430   - UserUpdater.jsx - \u0434\u043b\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f - hooks - \u0445\u0443\u043a\u0438   - useForm.js - \u0434\u043b\u044f \u0444\u043e\u0440\u043c\u044b   - useStore.js - \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f - pages - \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b   - About.jsx   - Blog.jsx - \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u043f\u043e\u0441\u0442\u043e\u0432   - Home.jsx   - Login.jsx - \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438   - Post.jsx - \u0434\u043b\u044f \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043e\u0441\u0442\u0430   - Profile.jsx - \u0434\u043b\u044f \u043f\u0440\u043e\u0444\u0438\u043b\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f   - Register.jsx - \u0434\u043b\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 - styles - \u0441\u0442\u0438\u043b\u0438 - supabase   - index.js - \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0438 \u044d\u043a\u0441\u043f\u043e\u0440\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 `Supabase` - utils - \u0443\u0442\u0438\u043b\u0438\u0442\u044b   - serializeUser.js - App.jsx - \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f - main.jsx - \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0444\u0430\u0439\u043b \u043a\u043b\u0438\u0435\u043d\u0442\u0430<\/code><\/pre>\n<p>  <\/p>\n<p>\u0421 \u0432\u0430\u0448\u0435\u0433\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0435\u043d\u0438\u044f, \u044f \u043d\u0435 \u0431\u0443\u0434\u0443 \u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0430 \u0441\u0442\u0438\u043b\u044f\u0445: \u0442\u0430\u043c \u043d\u0435\u0442 \u043d\u0438\u0447\u0435\u0433\u043e \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0433\u043e, \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u043a\u043e\u043f\u0438\u0440\u0443\u0439\u0442\u0435 \u0438\u0445 \u0438\u0437 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f.<\/p>\n<p>  <\/p>\n<p>\u041d\u0430\u0441\u0442\u0440\u043e\u0438\u043c \u0430\u043b\u0438\u0430\u0441\u044b (alias \u2014 \u0441\u0438\u043d\u043e\u043d\u0438\u043c) \u0434\u043b\u044f \u043e\u0431\u043b\u0435\u0433\u0447\u0435\u043d\u0438\u044f \u0438\u043c\u043f\u043e\u0440\u0442\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432 \u0432 <code>vite.config.js<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import react from '@vitejs\/plugin-react' import { dirname, resolve } from 'path' import { fileURLToPath } from 'url' import { defineConfig } from 'vite'  \/\/ \u0430\u0431\u0441\u043e\u043b\u044e\u0442\u043d\u044b\u0439 \u043f\u0443\u0442\u044c \u043a \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 const _dirname = dirname(fileURLToPath(import.meta.url))  export default defineConfig({   plugins: [react()],   resolve: {     alias: {       '@': resolve(_dirname, '.\/src'),       a: resolve(_dirname, '.\/src\/api'),       c: resolve(_dirname, '.\/src\/components'),       h: resolve(_dirname, '.\/src\/hooks'),       p: resolve(_dirname, '.\/src\/pages'),       s: resolve(_dirname, '.\/src\/supabase'),       u: resolve(_dirname, '.\/src\/utils')     }   } })<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0430\u0447\u043d\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043d\u0430\u0448\u0435\u0433\u043e \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0441 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 <code>API<\/code>.<\/p>\n<p>  <\/p>\n<h2 id=\"api\">API<\/h2>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b <code>API<\/code> \u043d\u0443\u0436\u0435\u043d \u043a\u043b\u0438\u0435\u043d\u0442 \u0434\u043b\u044f \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 <code>Supabase<\/code>.<\/p>\n<p>  <\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0438 \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u0435\u0433\u043e \u0432 <code>supabase\/index.js<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { createClient } from '@supabase\/supabase-js'  const supabase = createClient(   \/\/ \u0442\u0430\u043a\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u043c \u0441\u0440\u0435\u0434\u044b \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u043c \u0434\u043b\u044f `vite`   import.meta.env.VITE_SUPABASE_URL,   import.meta.env.VITE_SUPABASE_KEY )  export default supabase<\/code><\/pre>\n<p>  <\/p>\n<p><code>API<\/code> \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441\u043e \u0432\u0441\u0435\u043c\u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u043c\u0438 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 (<code>api\/db.js<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import supabase from 's'  \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0437 \u0432\u0441\u0435\u0445 \u0442\u0430\u0431\u043b\u0438\u0446 async function fetchAllData() {   try {     \/\/ \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438     const { data: users } = await supabase       .from('users')       .select('id, email, user_name')     \/\/ \u043f\u043e\u0441\u0442\u044b     const { data: posts } = await supabase       .from('posts')       .select('id, title, content, user_id, created_at')     \/\/ \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438     const { data: comments } = await supabase       .from('comments')       .select('id, content, user_id, post_id, created_at')     return { users, posts, comments }   } catch (e) {     console.error(e)   } }  const dbApi = { fetchAllData }  export default dbApi<\/code><\/pre>\n<p>  <\/p>\n<p>\u0423\u0442\u0438\u043b\u0438\u0442\u0430 \u0434\u043b\u044f \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f (<code>utils\/serializeUser.js<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">const serializeUser = (user) =>   user     ? {         id: user.id,         email: user.email,         ...user.user_metadata       }     : null  export default serializeUser<\/code><\/pre>\n<p>  <\/p>\n<p>\u0412\u0441\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439, \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c\u044b\u0435 \u043f\u0440\u0438 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438, \u043a\u0440\u043e\u043c\u0435 <code>email<\/code> \u0438 <code>password<\/code>, \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0432 \u043f\u043e\u043b\u0435 <code>user_metadata<\/code>, \u0447\u0442\u043e \u043d\u0435 \u043e\u0447\u0435\u043d\u044c \u0443\u0434\u043e\u0431\u043d\u043e.<\/p>\n<p>  <\/p>\n<p><code>API<\/code> \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0442\u0430\u0431\u043b\u0438\u0446\u0435\u0439 <code>users<\/code> \u2014 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 (<code>api\/user.js<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import supabase from 's' import serializeUser from 'u\/serializeUser'  \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u0437 \u0431\u0430\u0437\u044b \u043f\u0440\u0438 \u043d\u0430\u043b\u0438\u0447\u0438\u0438 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u0446\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \/\/ \u043e\u0431\u044a\u0435\u043a\u0442, \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u044b\u0439 \u043c\u0435\u0442\u043e\u0434\u043e\u043c `auth.user`, \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442\u0441\u044f \u0438\u0437 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 const get = async () => {   const user = supabase.auth.user()   if (user) {     try {       const { data, error } = await supabase         .from('users')         .select()         .match({ id: user.id })         .single()       if (error) throw error       console.log(data)       return data     } catch (e) {       throw e     }   }   return null }  \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f const register = async (data) => {   const { email, password, user_name } = data   try {     \/\/ \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f     const { user, error } = await supabase.auth.signUp(       \/\/ \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435\/\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435       {         email,         password       },       \/\/ \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435\/\u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435       {         data: {           user_name         }       }     )     if (error) throw error     \/\/ \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432 \u0431\u0430\u0437\u0443     const { data: _user, error: _error } = await supabase       .from('users')       \/\/ \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u043e\u0431\u044a\u0435\u043a\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f       .insert([serializeUser(user)])       .single()     if (_error) throw _error     return _user   } catch (e) {     throw e   } }  \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f const login = async (data) => {   try {     \/\/ \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f     const { user, error } = await supabase.auth.signIn(data)     if (error) throw error     \/\/ \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u0437 \u0431\u0430\u0437\u044b     const { data: _user, error: _error } = await supabase       .from('users')       .select()       .match({ id: user.id })       .single()     if (_error) throw _error     return _user   } catch (e) {     throw e   } }  \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0432\u044b\u0445\u043e\u0434\u0430 \u0438\u0437 \u0441\u0438\u0441\u0442\u0435\u043c\u044b const logout = async () => {   try {     const { error } = await supabase.auth.signOut()     if (error) throw error     return null   } catch (e) {     throw e   } }  \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f const update = async (data) => {   \/\/ \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043e\u0431\u044a\u0435\u043a\u0442 \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f   const user = supabase.auth.user()   if (!user) return   try {     const { data: _user, error } = await supabase       .from('users')       .update(data)       .match({ id: user.id })       .single()     if (error) throw error     return _user   } catch (e) {     throw e   } }  \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0430\u0432\u0430\u0442\u0430\u0440\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \/\/ \u0441\u043c. \u043d\u0438\u0436\u0435  const userApi = { get, register, login, logout, update, uploadAvatar }  export default userApi<\/code><\/pre>\n<p>  <\/p>\n<p>\u041c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0430\u0432\u0430\u0442\u0430\u0440\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">\/\/ \u0430\u0434\u0440\u0435\u0441 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 const STORAGE_URL =   `${import.meta.env.VITE_SUPABASE_URL}\/storage\/v1\/object\/public\/`  \/\/ \u043c\u0435\u0442\u043e\u0434 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0444\u0430\u0439\u043b - \u0430\u0432\u0430\u0442\u0430\u0440 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f const uploadAvatar = async (file) => {   const user = supabase.auth.user()   if (!user) return   const { id } = user   \/\/ \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 \u0438\u0437 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u0444\u0430\u0439\u043b\u0430   \/\/ \u043c\u0435\u0442\u043e\u0434 `at` \u043f\u043e\u044f\u0432\u0438\u043b\u0441\u044f \u0432 `ECMAScript` \u0432 \u044d\u0442\u043e\u043c \u0433\u043e\u0434\u0443   \/\/ \u043e\u043d \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u0440\u043e\u0441\u0442\u044b\u043c \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043c \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0442\u044c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u043c\u0430\u0441\u0441\u0438\u0432\u0430 \u0441 \u043a\u043e\u043d\u0446\u0430   const ext = file.name.split('.').at(-1)   \/\/ \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0430\u0432\u0430\u0442\u0430\u0440\u0430   const name = id + '.' + ext   try {     \/\/ \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u0444\u0430\u0439\u043b \u0432 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435     const {       \/\/ \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442 \u0438\u043c\u0435\u0435\u0442 \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0441\u0442\u0440\u0430\u043d\u043d\u0443\u044e \u0444\u043e\u0440\u043c\u0443       data: { Key },       error     } = await supabase.storage.from('avatars').upload(name, file, {       \/\/ \u043d\u0435 \u043a\u0435\u0448\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0444\u0430\u0439\u043b - \u044d\u0442\u043e \u0432\u0430\u0436\u043d\u043e!       cacheControl: 'no-cache',       \/\/ \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u0430\u0432\u0430\u0442\u0430\u0440 \u043f\u0440\u0438 \u043d\u0430\u043b\u0438\u0447\u0438\u0438       upsert: true     })     if (error) throw error     \/\/ \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u043f\u0443\u0442\u044c \u043a \u0444\u0430\u0439\u043b\u0443     const avatar_url = STORAGE_URL + Key     \/\/ \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f -     \/\/ \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u043f\u0443\u0442\u044c \u043a \u0430\u0432\u0430\u0442\u0430\u0440\u0443     const { data: _user, error: _error } = await supabase       .from('users')       .update({ avatar_url })       .match({ id })       .single()     if (_error) throw _error     \/\/ \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f     return _user   } catch (e) {     throw e   } }<\/code><\/pre>\n<p>  <\/p>\n<p><code>API<\/code> \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0442\u0430\u0431\u043b\u0438\u0446\u0435\u0439 <code>posts<\/code> \u2014 \u043f\u043e\u0441\u0442\u044b (<code>api\/post.js<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import supabase from 's'  \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043f\u043e\u0441\u0442\u0430 const create = async (postData) => {   const user = supabase.auth.user()   if (!user) return   try {     const { data, error } = await supabase       .from('posts')       .insert([postData])       .single()     if (error) throw error     return data   } catch (e) {     throw e   } }  \/\/ \u0434\u043b\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u0441\u0442\u0430 const update = async (data) => {   const user = supabase.auth.user()   if (!user) return   try {     const { data: _data, error } = await supabase       .from('posts')       .update({ ...postData })       .match({ id: data.id, user_id: user.id })     if (error) throw error     return _data   } catch (e) {     throw e   } }  \/\/ \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u0441\u0442\u0430 const remove = async (id) => {   const user = supabase.auth.user()   if (!user) return   try {     \/\/ \u0443\u0434\u0430\u043b\u044f\u0435\u043c \u043f\u043e\u0441\u0442     const { error } = await supabase       .from('posts')       .delete()       .match({ id, user_id: user.id })     if (error) throw error     \/\/ \u0443\u0434\u0430\u043b\u044f\u0435\u043c \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \u043a \u044d\u0442\u043e\u043c\u0443 \u043f\u043e\u0441\u0442\u0443     const { error: _error } = await supabase       .from('comments')       .delete()       .match({ post_id: id })     if (_error) throw _error   } catch (e) {     throw e   } }  const postApi = { create, update, remove }  export default postApi<\/code><\/pre>\n<p>  <\/p>\n<p><code>API<\/code> \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0442\u0430\u0431\u043b\u0438\u0446\u0435\u0439 <code>comments<\/code> \u2014 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 (<code>api\/comment.js<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import supabase from 's'  \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f const create = async (commentData) => {   const user = supabase.auth.user()   if (!user) return   try {     const { data, error } = await supabase       .from('comments')       .insert([{ ...commentData, user_id: user.id }])       .single()     if (error) throw error     return data   } catch (e) {     throw e   } }  \/\/ \u0434\u043b\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f const update = async (commentData) => {   const user = supabase.auth.user()   if (!user) return   try {     const { data, error } = await supabase       .from('comments')       .update({ ...commentData })       .match({ id: commentData.id, user_id: user.id })     if (error) throw error     return data   } catch (e) {     throw e   } }  \/\/ \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f const remove = async (id) => {   const user = supabase.auth.user()   if (!user) return   try {     const { error } = await supabase       .from('comments')       .delete()       .match({ id, user_id: user.id })     if (error) throw error   } catch (e) {     throw e   } }  const commentApi = { create, update, remove }  export default commentApi<\/code><\/pre>\n<p>  <\/p>\n<h2 id=\"huki\">\u0425\u0443\u043a\u0438<\/h2>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\u043c \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <code>zustand<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u0432 \u0444\u043e\u0440\u043c\u0435 \u0445\u0443\u043a\u0430.<\/p>\n<p>  <\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u0432 \u0444\u0430\u0439\u043b\u0435 <code>hooks\/useStore.js<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import create from 'zustand' import dbApi from 'a\/db' import postApi from 'a\/post'  const useStore = create((set, get) => ({   \/\/ \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438   loading: true,   \/\/ \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0434\u043b\u044f \u0435\u0433\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f   setLoading: (loading) => set({ loading }),   \/\/ \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043e\u0448\u0438\u0431\u043a\u0438   error: null,   \/\/ \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0434\u043b\u044f \u0435\u0433\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f   setError: (error) => set({ loading: false, error }),   \/\/ \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f   user: null,   \/\/ \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0434\u043b\u044f \u0435\u0433\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f   setUser: (user) => set({ user }),    \/\/ \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438   users: [],   \/\/ \u043f\u043e\u0441\u0442\u044b   posts: [],   \/\/ \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438   comments: [],    \/\/ \u043c\u044b \u043c\u043e\u0436\u0435\u043c \"\u0442\u0430\u0441\u043e\u0432\u0430\u0442\u044c\" \u043d\u0430\u0448\u0438 \u0434\u0430\u043d\u043d\u044b\u0435 \u043a\u0430\u043a \u0443\u0433\u043e\u0434\u043d\u043e,   \/\/ \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0442\u0430\u043a:   \/\/ \u043e\u0431\u044a\u0435\u043a\u0442 \u043f\u043e\u0441\u0442\u043e\u0432 \u0441 \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c \u043f\u043e `id` \u043f\u043e\u0441\u0442\u0430   postsById: {},   \/\/ \u043e\u0431\u044a\u0435\u043a\u0442 \u043f\u043e\u0441\u0442\u043e\u0432 \u0441 \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c \u043f\u043e `id` \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f   postsByUser: {},   \/\/ \u043a\u0430\u0440\u0442\u0430 \"\u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f - `id` \u043f\u043e\u0441\u0442\u0430\"   userByPost: {},   \/\/ \u043e\u0431\u044a\u0435\u043a\u0442 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432 \u0441 \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c \u043f\u043e `id` \u043f\u043e\u0441\u0442\u0430   commentsByPost: {},   \/\/ \u043c\u0430\u0441\u0441\u0438\u0432 \u0432\u0441\u0435\u0445 \u043f\u043e\u0441\u0442\u043e\u0432 \u0441 \u0430\u0432\u0442\u043e\u0440\u0430\u043c\u0438 \u0438 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e\u043c \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432   allPostsWithCommentCount: [],   \/\/ \u0434\u0430\u043b\u0435\u0435 \u0432\u0430\u0436\u043d\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0440\u044f\u0434\u043e\u043a \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445    \/\/ \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u043e\u0431\u044a\u0435\u043a\u0442 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432 \u0441 \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c \u043f\u043e `id` \u043f\u043e\u0441\u0442\u0430   getCommentsByPost() {     const { users, posts, comments } = get()     const commentsByPost = posts.reduce((obj, post) => {       obj[post.id] = comments         .filter((comment) => comment.post_id === post.id)         .map((comment) => ({           ...comment,           \/\/ \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0432 \u043e\u0431\u044a\u0435\u043a\u0442 \u0430\u0432\u0442\u043e\u0440\u0430           author: users.find((user) => user.id === comment.user_id).user_name         }))       return obj     }, {})     set({ commentsByPost })   },   \/\/ \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u043a\u0430\u0440\u0442\u0443 \"\u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f - `id` \u043f\u043e\u0441\u0442\u0430\"   getUserByPost() {     const { users, posts } = get()     const userByPost = posts.reduce((obj, post) => {       obj[post.id] = users.find((user) => user.id === post.user_id).user_name       return obj     }, {})     set({ userByPost })   },   \/\/ \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u043e\u0431\u044a\u0435\u043a\u0442 \u043f\u043e\u0441\u0442\u043e\u0432 \u0441 \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c \u043f\u043e `id` \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f   getPostsByUser() {     \/\/ \u0437\u0434\u0435\u0441\u044c \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0440\u0430\u043d\u0435\u0435 \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442 `commentsByPost`     const { users, posts, commentsByPost } = get()     const postsByUser = users.reduce((obj, user) => {       obj[user.id] = posts         .filter((post) => post.user_id === user.id)         .map((post) => ({           ...post,           \/\/ \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043c\u043e\u0436\u0435\u0442 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u0443\u0434\u0430\u043b\u044f\u0442\u044c \u0441\u0432\u043e\u0438 \u043f\u043e\u0441\u0442\u044b           editable: true,           \/\/ \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0432 \u043e\u0431\u044a\u0435\u043a\u0442 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432           commentCount: commentsByPost[post.id].length         }))       return obj     }, {})     set({ postsByUser })   },   \/\/ \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u043e\u0431\u044a\u0435\u043a\u0442 \u043f\u043e\u0441\u0442\u043e\u0432 \u0441 \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c \u043f\u043e `id` \u043f\u043e\u0441\u0442\u0430   getPostsById() {     \/\/ \u0437\u0434\u0435\u0441\u044c \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0440\u0430\u043d\u0435\u0435 \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b `userByPost` \u0438 `commentsByPost`     const { posts, user, userByPost, commentsByPost } = get()     const postsById = posts.reduce((obj, post) => {       obj[post.id] = {         ...post,         \/\/ \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0432 \u043e\u0431\u044a\u0435\u043a\u0442 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438         comments: commentsByPost[post.id],         \/\/ \u0438 \u0438\u0445 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e         commentCount: commentsByPost[post.id].length       }       \/\/ \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 \u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0439 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 (`?.`)       \/\/ \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043c\u043e\u0436\u0435\u0442 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c (`null`)        \/\/ \u0435\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u0440\u043e\u043c \u043f\u043e\u0441\u0442\u0430       if (post.user_id === user?.id) {         \/\/ \u0437\u043d\u0430\u0447\u0438\u0442, \u043e\u043d \u043c\u043e\u0436\u0435\u0442 \u0435\u0433\u043e \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u0443\u0434\u0430\u043b\u044f\u0442\u044c         obj[post.id].editable = true       \/\/ \u0438\u043d\u0430\u0447\u0435       } else {         \/\/ \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0432 \u043e\u0431\u044a\u0435\u043a\u0442 \u0438\u043c\u044f \u0430\u0432\u0442\u043e\u0440\u0430 \u043f\u043e\u0441\u0442\u0430         obj[post.id].author = userByPost[post.id]       }       return obj     }, {})     set({ postsById })   },   \/\/ \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u043c\u0430\u0441\u0441\u0438\u0432 \u0432\u0441\u0435\u0445 \u043f\u043e\u0441\u0442\u043e\u0432 \u0441 \u0430\u0432\u0442\u043e\u0440\u0430\u043c\u0438 \u0438 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u043c\u0438   getAllPostsWithCommentCount() {     \/\/ \u0437\u0434\u0435\u0441\u044c \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0440\u0430\u043d\u0435\u0435 \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b `userByPost` \u0438 `commentsByPost`     const { posts, user, userByPost, commentsByPost } = get()     const allPostsWithCommentCount = posts.map((post) => ({       ...post,       \/\/ \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043b\u0438 \u043f\u043e\u0441\u0442 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u043c\u044b\u043c       editable: user?.id === post.user_id,       \/\/ \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0432 \u043e\u0431\u044a\u0435\u043a\u0442 \u0430\u0432\u0442\u043e\u0440\u0430       author: userByPost[post.id],       \/\/ \u0438 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432       commentCount: commentsByPost[post.id].length     }))     set({ allPostsWithCommentCount })   },    \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0432\u0441\u0435\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0438 \u043c\u0430\u0441\u0441\u0438\u0432\u0430   async fetchAllData() {     set({ loading: true })      const {       getCommentsByPost,       getUserByPost,       getPostsByUser,       getPostsById,       getAllPostsWithCommentCount     } = get()      const { users, posts, comments } = await dbApi.fetchAllData()      set({ users, posts, comments })      getCommentsByPost()     getPostsByUser()     getUserByPost()     getPostsById()     getAllPostsWithCommentCount()      set({ loading: false })   },    \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u0441\u0442\u0430   \/\/ \u0434\u0430\u043d\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u043c, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u0440\u0430\u0437\u043d\u044b\u0445 \u0443\u0440\u043e\u0432\u043d\u044f\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f   removePost(id) {     set({ loading: true })     postApi.remove(id).catch((error) => set({ error }))   } }))  export default useStore<\/code><\/pre>\n<p>  <\/p>\n<p>\u0425\u0443\u043a \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0444\u043e\u0440\u043c\u0430\u043c\u0438 (<code>hooks\/useForm.js<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { useState, useEffect } from 'react'  \/\/ \u0445\u0443\u043a \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0444\u043e\u0440\u043c\u044b \/\/ \u0447\u0442\u043e\u0431\u044b \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043e\u0431\u043b\u0435\u0433\u0447\u0438\u0442\u044c \u0441\u0435\u0431\u0435 \u0436\u0438\u0437\u043d\u044c, \/\/ \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u0445\u043e\u0434\u0438\u0442\u044c \u0438\u0437 \u043f\u0440\u0435\u0434\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \/\/ \u0447\u0442\u043e \u0432\u0441\u0435 \u043f\u043e\u043b\u044f \u0444\u043e\u0440\u043c\u044b \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u043c\u0438 export default function useForm(initialData) {   const [data, setData] = useState(initialData)   const [disabled, setDisabled] = useState(true)    useEffect(() => {     \/\/ \u0435\u0441\u043b\u0438 \u043a\u0430\u043a\u043e\u0435-\u043b\u0438\u0431\u043e \u0438\u0437 \u043f\u043e\u043b\u0435\u0439 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0443\u0441\u0442\u044b\u043c     setDisabled(!Object.values(data).every(Boolean))   }, [data])    \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u0435\u0439 \u0444\u043e\u0440\u043c\u044b   const change = ({ target: { name, value } }) => {     setData({ ...data, [name]: value })   }    return { data, change, disabled } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043f\u043e\u043b\u044f \u0444\u043e\u0440\u043c\u044b \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c (<code>components\/Field.jsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">export const Field = ({ label, value, change, id, type, ...rest }) => (   &lt;div className='field'>     &lt;label htmlFor={id}>{label}&lt;\/label>     &lt;input       type={type}       id={id}       name={id}       required       value={value}       onChange={change}       {...rest}     \/>   &lt;\/div> )<\/code><\/pre>\n<p>  <\/p>\n<p>\u0410 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0444\u043e\u0440\u043c\u044b \u0442\u0430\u043a (<code>components\/Form.jsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import useForm from 'h\/useForm' import { Field } from '.\/Field'  \/\/ \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u043c\u0430\u0441\u0441\u0438\u0432 \u043f\u043e\u043b\u0435\u0439 \u0444\u043e\u0440\u043c\u044b, \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0444\u043e\u0440\u043c\u044b \u0438 \u043f\u043e\u0434\u043f\u0438\u0441\u044c \u043a \u043a\u043d\u043e\u043f\u043a\u0435 \u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0444\u043e\u0440\u043c\u044b export const Form = ({ fields, submit, button }) => {   \/\/ \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u043e\u043b\u044f \u043c\u043e\u0433\u0443\u0442 \u0438\u043c\u0435\u0442\u044c \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f,   \/\/ \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u0440\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f   const initialData = fields.reduce((o, f) => {     o[f.id] = f.value || ''     return o   }, {})   \/\/ \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0445\u0443\u043a   const { data, change, disabled } = useForm(initialData)    \/\/ \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0444\u043e\u0440\u043c\u044b   const onSubmit = (e) => {     if (disabled) return     e.preventDefault()     submit(data)   }    return (     &lt;form onSubmit={onSubmit}>       {fields.map((f) => (         &lt;Field key={f.id} {...f} value={data[f.id]} change={change} \/>       ))}       &lt;button disabled={disabled} className='success'>         {button}       &lt;\/button>     &lt;\/form>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043f\u0440\u0438\u043c\u0435\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0445\u0443\u043a\u0430 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0444\u043e\u0440\u043c\u044b \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u0434\u043b\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f (<code>pages\/Register.jsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import userApi from 'a\/user' import { Form } from 'c' import useStore from 'h\/useStore' import { useNavigate } from 'react-router-dom'  \/\/ \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0444\u043e\u0440\u043c\u044b const fields = [   {     id: 'user_name',     label: 'Username',     type: 'text'   },   {     id: 'email',     label: 'Email',     type: 'email'   },   {     id: 'password',     label: 'Password',     type: 'password'   },   {     id: 'confirm_password',     label: 'Confirm password',     type: 'password'   } ]  export const Register = () => {   \/\/ \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u0438\u0437 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043c\u0435\u0442\u043e\u0434\u044b \u0434\u043b\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0438 \u043e\u0448\u0438\u0431\u043a\u0438   const { setUser, setLoading, setError } = useStore(     ({ setUser, setLoading, setError }) => ({ setUser, setLoading, setError })   )   \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0440\u0443\u0447\u043d\u043e\u0433\u043e \u043f\u0435\u0440\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f   const navigate = useNavigate()    \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438   const register = async (data) => {     setLoading(true)     userApi       \/\/ \u0434\u0430\u043d\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043e\u0431\u044a\u0435\u043a\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f       .register(data)       .then((user) => {         \/\/ \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f         setUser(user)         \/\/ \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043f\u0435\u0440\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043d\u0430 \u0433\u043b\u0430\u0432\u043d\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443         navigate('\/')       })       \/\/ \u043e\u0448\u0438\u0431\u043a\u0430       .catch(setError)   }    return (     &lt;div className='page register'>       &lt;h1>Register&lt;\/h1>       &lt;Form fields={fields} submit={register} button='Register' \/>     &lt;\/div>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043f\u043e\u0445\u043e\u0436\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c (<code>pages\/Login.jsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import userApi from 'a\/user' import { Form } from 'c' import useStore from 'h\/useStore' import { useNavigate } from 'react-router-dom'  const fields = [   {     id: 'email',     label: 'Email',     type: 'email'   },   {     id: 'password',     label: 'Password',     type: 'password'   } ]  export const Login = () => {   const { setUser, setLoading, setError } = useStore(     ({ setUser, setLoading, setError }) => ({ setUser, setLoading, setError })   )   const navigate = useNavigate()    const register = async (data) => {     setLoading(true)     userApi       .login(data)       .then((user) => {         setUser(user)         navigate('\/')       })       .catch(setError)   }    return (     &lt;div className='page login'>       &lt;h1>Login&lt;\/h1>       &lt;Form fields={fields} submit={register} button='Login' \/>     &lt;\/div>   ) }<\/code><\/pre>\n<p>  <\/p>\n<h2 id=\"obrabotka-izmeneniya-dannyh-v-rezhime-realnogo-vremeni\">\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438<\/h2>\n<p>  <\/p>\n<p>\u0412\u044b \u043c\u043e\u0433\u043b\u0438 \u0437\u0430\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445 \u0434\u043b\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043c\u044b \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u0440\u0430\u0437 (<code>setLoading(true)<\/code>). \u0420\u0430\u0437\u0432\u0435 \u044d\u0442\u043e \u043d\u0435 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u0442 \u043a \u0442\u043e\u043c\u0443, \u0447\u0442\u043e \u0432\u0441\u0435 \u0432\u0440\u0435\u043c\u044f \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438? \u0418\u043c\u0435\u043d\u043d\u043e \u0442\u0430\u043a. \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u044d\u0442\u043e \u0438\u0441\u043f\u0440\u0430\u0432\u0438\u043c.<\/p>\n<p>  <\/p>\n<p>\u041f\u0440\u0438 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438\/\u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043f\u0440\u0438 \u043b\u044e\u0431\u044b\u0445 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f\u0445 \u0447\u0442\u0435\u043d\u0438\u044f\/\u0437\u0430\u043f\u0438\u0441\u0438 \u0432 \u0411\u0414 \u043c\u044b \u0445\u043e\u0442\u0438\u043c \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434 <code>fetchAllData<\/code> \u0438\u0437 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 (\u0432 \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u043d\u0435 \u0442\u0430\u043a \u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0435 \u043d\u0430\u0434\u043e).<\/p>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u043a\u043b\u0438\u0435\u043d\u0442 <code>Supabase<\/code> \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043c\u0435\u0442\u043e\u0434 <code>auth.onAuthStateChanged<\/code>, \u0430 \u0434\u043b\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439 \u043f\u043e \u0440\u0430\u0431\u043e\u0442\u0435 \u0441 \u0411\u0414 \u2014 \u043c\u0435\u0442\u043e\u0434 <code>from(tableNames).on(eventTypes, callback).subscribe<\/code>.<\/p>\n<p>  <\/p>\n<p>\u041e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u0444\u0430\u0439\u043b <code>supabase\/index.js<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { createClient } from '@supabase\/supabase-js' import useStore from 'h\/useStore'  const supabase = createClient(   import.meta.env.VITE_SUPABASE_URL,   import.meta.env.VITE_SUPABASE_KEY )  \/\/ \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 supabase.auth.onAuthStateChange((event, session) => {   console.log(event, session)   \/\/ \u043e\u0434\u043d\u043e\u0439 \u0438\u0437 \u043f\u0440\u0435\u043b\u0435\u0441\u0442\u0435\u0439 `zustand` \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0442\u043e,   \/\/ \u0447\u0442\u043e \u043c\u0435\u0442\u043e\u0434\u044b \u0438\u0437 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 \u043c\u043e\u0433\u0443\u0442 \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u0433\u0434\u0435 \u0443\u0433\u043e\u0434\u043d\u043e   useStore.getState().fetchAllData() })  \/\/ \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u0431\u0430\u0437\u0435 supabase   \/\/ \u043d\u0430\u0441 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u044e\u0442 \u0432\u0441\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b   .from('*')   \/\/ \u0438 \u0432\u0441\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438   .on('*', (payload) => {     console.log(payload)      useStore.getState().fetchAllData()   })   .subscribe()  export default supabase<\/code><\/pre>\n<p>  <\/p>\n<p>\u041a\u0430\u043a \u043c\u044b \u043f\u043e\u043c\u043d\u0438\u043c, \u043d\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0439 \u0441\u0442\u0440\u043e\u043a\u0435 \u043a\u043e\u0434\u0430 \u043c\u0435\u0442\u043e\u0434\u0430 <code>fetchAllData<\/code> \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f <code>set({ loading: false })<\/code>. \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u0434\u043e \u0442\u0435\u0445 \u043f\u043e\u0440, \u043f\u043e\u043a\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0432\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0438 \u043d\u0435 \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u0442 \u0432\u0441\u0435 \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0438 \u043c\u0430\u0441\u0441\u0438\u0432. \u0412 \u0441\u0432\u043e\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c, \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0432\u0441\u0435\u0433\u0434\u0430 \u0431\u0443\u0434\u0435\u0442 \u0438\u043c\u0435\u0442\u044c \u0434\u0435\u043b\u043e \u0441 \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438.<\/p>\n<p>  <\/p>\n<p><em>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435<\/em> \u043d\u0430 \u0442\u043e, \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u043d\u0430\u044f \u043d\u0430\u043c\u0438 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043d\u0435 \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442 \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0431\u043e\u043b\u044c\u0448\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0434\u0430\u043d\u043d\u044b\u0445: \u0431\u043e\u043b\u044c\u0448\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0434\u0430\u043d\u043d\u044b\u0445 \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u0434\u043e\u043b\u0433\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u0438\u0445 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438. \u0412 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u0445 \u0432\u043c\u0435\u0441\u0442\u043e \u043f\u0440\u044f\u043c\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u0431\u0430\u0437\u0443 \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0442\u044c \u043e\u043f\u0442\u0438\u043c\u0438\u0441\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f.<\/p>\n<p>  <\/p>\n<h2 id=\"stranicy-i-komponenty\">\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b<\/h2>\n<p>  <\/p>\n<p>\u0421 \u0432\u0430\u0448\u0435\u0433\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0435\u043d\u0438\u044f, \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u0442\u043e\u043b\u044c\u043a\u043e \u043e \u0442\u0435\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0447\u0442\u043e-\u043d\u0438\u0431\u0443\u0434\u044c \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e\u0435.<\/p>\n<p>  <\/p>\n<p>\u041d\u0430\u0447\u043d\u0435\u043c \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043f\u0440\u043e\u0444\u0438\u043b\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f (<code>pages\/Profile.jsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { Protected, UserUpdater } from 'c' import useStore from 'h\/useStore'  export const Profile = () => {   \/\/ \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u0438\u0437 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 \u043e\u0431\u044a\u0435\u043a\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f   const user = useStore(({ user }) => user)   \/\/ \u043a\u043e\u043f\u0438\u0440\u0443\u0435\u043c \u0435\u0433\u043e   const userCopy = { ...user }   \/\/ \u0438 \u0443\u0434\u0430\u043b\u044f\u0435\u043c \u043f\u043e\u043b\u0435 \u0441 \u0430\u0434\u0440\u0435\u0441\u043e\u043c \u0430\u0432\u0430\u0442\u0430\u0440\u0430 -   \/\/ \u043e\u043d \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u0434\u043b\u0438\u043d\u043d\u044b\u0439 \u0438 \u043b\u043e\u043c\u0430\u0435\u0442 \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0443   delete userCopy.avatar_url    return (     \/\/ \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u043d\u043e\u0439     &lt;Protected className='page profile'>       &lt;h1>Profile&lt;\/h1>       &lt;div className='user-data'>         {\/* \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f *\/}         &lt;pre>{JSON.stringify(userCopy, null, 2)}&lt;\/pre>       &lt;\/div>       {\/* \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0434\u043b\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f *\/}       &lt;UserUpdater \/>     &lt;\/Protected>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <code>Protected<\/code> \u043f\u0435\u0440\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u043d\u0435\u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0430 \u0433\u043b\u0430\u0432\u043d\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f (<code>components\/Protected.jsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import useStore from 'h\/useStore' import { useEffect } from 'react' import { useNavigate } from 'react-router-dom'  export const Protected = ({ children, className }) => {   const { user, loading } = useStore(({ user, loading }) => ({ user, loading }))   const navigate = useNavigate()    useEffect(() => {     if (!loading &amp;&amp; !user) {       navigate('\/')     }   }, [user, loading])    \/\/ \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043c \u043f\u0440\u0438 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f   if (!user) return null    return &lt;div className={className ? className : ''}>{children}&lt;\/div> }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0434\u043b\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f (<code>components\/UserUpdater.jsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { Form, AvatarUploader } from 'c' import useStore from 'h\/useStore' import userApi from 'a\/user'  export const UserUpdater = () => {   const { user, setUser, setLoading, setError } = useStore(     ({ user, setUser, setLoading, setError }) => ({       user,       setUser,       setLoading,       setError     })   )    \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f   const updateUser = async (data) => {     setLoading(true)     userApi.update(data).then(setUser).catch(setError)   }    \/\/ \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435   \/\/ \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u0438\u0437 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f   const fields = [     {       id: 'first_name',       label: 'First Name',       type: 'text',       value: user.first_name     },     {       id: 'last_name',       label: 'Last Name',       type: 'text',       value: user.last_name     },     {       id: 'age',       label: 'Age',       type: 'number',       value: user.age     }   ]    return (     &lt;div className='user-updater'>       &lt;h2>Update User&lt;\/h2>       {\/* \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0430\u0432\u0430\u0442\u0430\u0440\u0430 *\/}       &lt;AvatarUploader \/>       &lt;h3>User Bio&lt;\/h3>       &lt;Form fields={fields} submit={updateUser} button='Update' \/>     &lt;\/div>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0430\u0432\u0430\u0442\u0430\u0440\u0430 (<code>components\/AvatarUploader.jsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { useState, useEffect } from 'react' import userApi from 'a\/user' import useStore from 'h\/useStore'  export const AvatarUploader = () => {   const { user, setUser, setLoading, setError } = useStore(     ({ user, setUser, setLoading, setError }) => ({       user,       setUser,       setLoading,       setError     })   )   \/\/ \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u043b\u044f \u0444\u0430\u0439\u043b\u0430   const [file, setFile] = useState('')   const [disabled, setDisabled] = useState(true)    useEffect(() => {     setDisabled(!file)   }, [file])    const upload = (e) => {     e.preventDefault()     if (disabled) return     setLoading(true)     userApi.uploadAvatar(file).then(setUser).catch(setError)   }    return (     &lt;div className='avatar-uploader'>       &lt;form className='avatar-uploader' onSubmit={upload}>         &lt;label htmlFor='avatar'>Avatar:&lt;\/label>         &lt;input           type='file'           \/\/ \u0438\u043d\u043f\u0443\u0442 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f           accept='image\/*'           onChange={(e) => {             if (e.target.files) {               setFile(e.target.files[0])             }           }}         \/>         &lt;button disabled={disabled}>Upload&lt;\/button>       &lt;\/form>     &lt;\/div>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0434\u043b\u044f \u043f\u043e\u0441\u0442\u043e\u0432 (<code>pages\/Blog.jsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import postApi from 'a\/post' import { Form, PostList, PostTabs, Protected } from 'c' import useStore from 'h\/useStore' import { useEffect, useState } from 'react'  \/\/ \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u0441\u0442\u0430 const fields = [   {     id: 'title',     label: 'Title',     type: 'text'   },   {     id: 'content',     label: 'Content',     type: 'text'   } ]  export const Blog = () => {   const { user, allPostsWithCommentCount, postsByUser, setLoading, setError } =     useStore(       ({         user,         allPostsWithCommentCount,         postsByUser,         setLoading,         setError       }) => ({         user,         allPostsWithCommentCount,         postsByUser,         setLoading,         setError       })     )   \/\/ \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u0430\u044f \u0432\u043a\u043b\u0430\u0434\u043a\u0430   const [tab, setTab] = useState('all')   \/\/ \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u043b\u044f \u043e\u0442\u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u0438 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0439 \u0432\u043a\u043b\u0430\u0434\u043a\u0438 \u043f\u043e\u0441\u0442\u043e\u0432   const [_posts, setPosts] = useState([])    \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043d\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u0441\u0442\u0430   const create = (data) => {     setLoading(true)     postApi       .create(data)       .then(() => {         \/\/ \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0432\u043a\u043b\u0430\u0434\u043a\u0443         setTab('my')       })       .catch(setError)   }    useEffect(() => {     if (tab === 'new') return     \/\/ \u0444\u0438\u043b\u044c\u0442\u0440\u0443\u0435\u043c \u043f\u043e\u0441\u0442\u044b \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u0438 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0439 \u0432\u043a\u043b\u0430\u0434\u043a\u0438     const _posts =       tab === 'my' ? postsByUser[user.id] : allPostsWithCommentCount     setPosts(_posts)   }, [tab, allPostsWithCommentCount])    \/\/ \u0435\u0441\u043b\u0438 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u043c \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0439 \u0432\u043a\u043b\u0430\u0434\u043a\u0438 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f `new`,   \/\/ \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u0444\u043e\u0440\u043c\u0443 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043d\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u0441\u0442\u0430   \/\/ \u0434\u0430\u043d\u043d\u0430\u044f \u0432\u043a\u043b\u0430\u0434\u043a\u0430 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u043d\u043e\u0439   if (tab === 'new') {     return (       &lt;Protected className='page new-post'>         &lt;h1>Blog&lt;\/h1>         &lt;PostTabs tab={tab} setTab={setTab} \/>         &lt;h2>New post&lt;\/h2>         &lt;Form fields={fields} submit={create} button='Create' \/>       &lt;\/Protected>     )   }    return (     &lt;div className='page blog'>       &lt;h1>Blog&lt;\/h1>       &lt;PostTabs tab={tab} setTab={setTab} \/>       &lt;h2>{tab === 'my' ? 'My' : 'All'} posts&lt;\/h2>       &lt;PostList posts={_posts} \/>     &lt;\/div>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0412\u043a\u043b\u0430\u0434\u043a\u0438 \u043f\u043e\u0441\u0442\u043e\u0432 (<code>components\/PostTabs.jsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import useStore from 'h\/useStore'  \/\/ \u0432\u043a\u043b\u0430\u0434\u043a\u0438 \/\/ \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e `protected` \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442, \/\/ \u043a\u0430\u043a\u0438\u0435 \u0432\u043a\u043b\u0430\u0434\u043a\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e const tabs = [   {     name: 'All'   },   {     name: 'My',     protected: true   },   {     name: 'New',     protected: true   } ]  export const PostTabs = ({ tab, setTab }) => {   const user = useStore(({ user }) => user)    return (     &lt;nav className='post-tabs'>       &lt;ul>         {tabs.map((t) => {           const tabId = t.name.toLowerCase()           if (t.protected) {             return user ? (               &lt;li key={tabId}>                 &lt;button                   className={tab === tabId ? 'active' : ''}                   onClick={() => setTab(tabId)}                 >                   {t.name}                 &lt;\/button>               &lt;\/li>             ) : null           }           return (             &lt;li key={tabId}>               &lt;button                 className={tab === tabId ? 'active' : ''}                 onClick={() => setTab(tabId)}               >                 {t.name}               &lt;\/button>             &lt;\/li>           )         })}       &lt;\/ul>     &lt;\/nav>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0421\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u0441\u0442\u043e\u0432 (<code>components\/PostList.jsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { Link, useNavigate } from 'react-router-dom' import useStore from 'h\/useStore' import { VscComment, VscEdit, VscTrash } from 'react-icons\/vsc'  \/\/ \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u043f\u043e\u0441\u0442\u0430 const PostItem = ({ post }) => {   const removePost = useStore(({ removePost }) => removePost)   const navigate = useNavigate()    \/\/ \u043a\u0430\u0436\u0434\u044b\u0439 \u043f\u043e\u0441\u0442 - \u044d\u0442\u043e \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u0435\u0433\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443   return (     &lt;Link       to={`\/blog\/post\/${post.id}`}       className='post-item'       onClick={(e) => {         \/\/ \u043e\u0442\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u043f\u0435\u0440\u0435\u0445\u043e\u0434 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u043f\u043e\u0441\u0442\u0430         \/\/ \u043f\u0440\u0438 \u043a\u043b\u0438\u043a\u0435 \u043f\u043e \u043a\u043d\u043e\u043f\u043a\u0435 \u0438\u043b\u0438 \u0438\u043a\u043e\u043d\u043a\u0435         if (e.target.localName === 'button' || e.target.localName === 'svg') {           e.preventDefault()         }       }}     >       &lt;h3>{post.title}&lt;\/h3>       {\/* \u0435\u0441\u043b\u0438 \u043f\u043e\u0441\u0442 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u043c\u044b\u043c - \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0438\u0442 \u0442\u0435\u043a\u0443\u0449\u0435\u043c\u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e *\/}       {post.editable &amp;&amp; (         &lt;div>           &lt;button             onClick={() => {               \/\/ \u0441\u0442\u0440\u043e\u043a\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 `edit=true` \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442,               \/\/ \u0447\u0442\u043e \u043f\u043e\u0441\u0442 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f               navigate(`\/blog\/post\/${post.id}?edit=true`)             }}             className='info'           >             &lt;VscEdit \/>           &lt;\/button>           &lt;button             onClick={() => {               removePost(post.id)             }}             className='danger'           >             &lt;VscTrash \/>           &lt;\/button>         &lt;\/div>       )}       &lt;p>Author: {post.author}&lt;\/p>       &lt;p className='date'>{new Date(post.created_at).toLocaleString()}&lt;\/p>       {\/* \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432 \u043a \u043f\u043e\u0441\u0442\u0443 *\/}       {post.commentCount > 0 &amp;&amp; (         &lt;p>           &lt;VscComment \/>           &lt;span className='badge'>             &lt;sup>{post.commentCount}&lt;\/sup>           &lt;\/span>         &lt;\/p>       )}     &lt;\/Link>   ) }  \/\/ \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u043e\u0441\u0442\u043e\u0432 export const PostList = ({ posts }) => (   &lt;div className='post-list'>     {posts.length > 0 ? (       posts.map((post) => &lt;PostItem key={post.id} post={post} \/>)     ) : (       &lt;h3>No posts&lt;\/h3>     )}   &lt;\/div> )<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u044f\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043c\u044b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u2014 \u044d\u0442\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043f\u043e\u0441\u0442\u0430 (<code>pages\/Post.jsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import postApi from 'a\/post' import commentApi from 'a\/comment' import { Form, Protected, CommentList } from 'c' import useStore from 'h\/useStore' import { useNavigate, useParams, useLocation } from 'react-router-dom' import { VscEdit, VscTrash } from 'react-icons\/vsc'  \/\/ \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0434\u043b\u044f \u043d\u043e\u0432\u043e\u0433\u043e \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f const createCommentFields = [   {     id: 'content',     label: 'Content',     type: 'text'   } ]  export const Post = () => {   const { user, setLoading, setError, postsById, removePost } = useStore(     ({ user, setLoading, setError, postsById, removePost }) => ({       user,       setLoading,       setError,       postsById,       removePost     })   )   \/\/ \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c `id` \u043f\u043e\u0441\u0442\u0430 \u0438\u0437 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432   const { id } = useParams()   const { search } = useLocation()   \/\/ \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u043e\u0441\u0442\u0430 \u0438\u0437 \u0441\u0442\u0440\u043e\u043a\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430   const edit = new URLSearchParams(search).get('edit')   \/\/ \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u043f\u043e\u0441\u0442 \u043f\u043e \u0435\u0433\u043e `id`   const post = postsById[id]   const navigate = useNavigate()    \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u0441\u0442\u0430   const updatePost = (data) => {     setLoading(true)     data.id = post.id     postApi       .update(data)       .then(() => {         \/\/ \u0442\u0430 \u0436\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430, \u043d\u043e \u0431\u0435\u0437 \u0441\u0442\u0440\u043e\u043a\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430         navigate(`\/blog\/post\/${post.id}`)       })       .catch(setError)   }    \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f   const createComment = (data) => {     setLoading(true)     data.post_id = post.id     commentApi.create(data).catch(setError)   }    \/\/ \u0435\u0441\u043b\u0438 \u043f\u043e\u0441\u0442 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f   if (edit) {     const editPostFields = [       {         id: 'title',         label: 'Title',         type: 'text',         value: post.title       },       {         id: 'content',         label: 'Content',         type: 'text',         value: post.content       }     ]      return (       &lt;Protected>         &lt;h2>Update post&lt;\/h2>         &lt;Form fields={editPostFields} submit={updatePost} button='Update' \/>       &lt;\/Protected>     )   }    return (     &lt;div className='page post'>       &lt;h1>Post&lt;\/h1>       {post &amp;&amp; (         &lt;div className='post-item' style={{ width: '512px' }}>           &lt;h2>{post.title}&lt;\/h2>           {post.editable ? (             &lt;div>               &lt;button                 onClick={() => {                   navigate(`\/blog\/post\/${post.id}?edit=true`)                 }}                 className='info'               >                 &lt;VscEdit \/>               &lt;\/button>               &lt;button                 onClick={() => {                   removePost(post.id)                   navigate('\/blog')                 }}                 className='danger'               >                 &lt;VscTrash \/>               &lt;\/button>             &lt;\/div>           ) : (             &lt;p>Author: {post.author}&lt;\/p>           )}           &lt;p className='date'>{new Date(post.created_at).toLocaleString()}&lt;\/p>           &lt;p>{post.content}&lt;\/p>           {user &amp;&amp; (             &lt;div className='new-comment'>               &lt;h3>New comment&lt;\/h3>               &lt;Form                 fields={createCommentFields}                 submit={createComment}                 button='Create'               \/>             &lt;\/div>           )}           {\/* \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \u043a \u043f\u043e\u0441\u0442\u0443 *\/}           {post.comments.length > 0 &amp;&amp; &lt;CommentList comments={post.comments} \/>}         &lt;\/div>       )}     &lt;\/div>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0434\u043b\u044f \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432 \u043a \u043f\u043e\u0441\u0442\u0443 (<code>components\/CommentList.jsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { useState } from 'react' import useStore from 'h\/useStore' import commentApi from 'a\/comment' import { Form, Protected } from 'c' import { VscEdit, VscTrash } from 'react-icons\/vsc'  export const CommentList = ({ comments }) => {   const { user, setLoading, setError } = useStore(     ({ user, setLoading, setError }) => ({ user, setLoading, setError })   )   \/\/ \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f   const [editComment, setEditComment] = useState(null)    \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f   const remove = (id) => {     setLoading(true)     commentApi.remove(id).catch(setError)   }    \/\/ \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f   const update = (data) => {     setLoading(true)     data.id = editComment.id     commentApi.update(data).catch(setError)   }    \/\/ \u0435\u0441\u043b\u0438 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f   if (editComment) {     const fields = [       {         id: 'content',         label: 'Content',         type: 'text',         value: editComment.content       }     ]      return (       &lt;Protected>         &lt;h3>Update comment&lt;\/h3>         &lt;Form fields={fields} submit={update} button='Update' \/>       &lt;\/Protected>     )   }    return (     &lt;div className='comment-list'>       &lt;h3>Comments&lt;\/h3>       {comments.map((comment) => (         &lt;div className='comment-item' key={comment.id}>           &lt;p>{comment.content}&lt;\/p>           {\/* \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043b\u0438 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u043c\u044b\u043c - \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0438\u0442 \u043b\u0438 \u0442\u0435\u043a\u0443\u0449\u0435\u043c\u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e? *\/}           {comment.user_id === user?.id ? (             &lt;div>               &lt;button onClick={() => setEditComment(comment)} className='info'>                 &lt;VscEdit \/>               &lt;\/button>               &lt;button onClick={() => remove(comment.id)} className='danger'>                 &lt;VscTrash \/>               &lt;\/button>             &lt;\/div>           ) : (             &lt;p className='author'>Author: {comment.author}&lt;\/p>           )}           &lt;p className='date'>             {new Date(comment.created_at).toLocaleString()}           &lt;\/p>         &lt;\/div>       ))}     &lt;\/div>   ) }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0418 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f (<code>App.jsx<\/code>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import '.\/styles\/app.scss' import { Routes, Route } from 'react-router-dom' import { Home, About, Register, Login, Profile, Blog, Post } from 'p' import { Nav, Layout } from 'c' import { useEffect } from 'react' import useStore from 'h\/useStore' import userApi from 'a\/user'  function App() {   const { user, setUser, setLoading, setError, fetchAllData } = useStore(     ({ user, setUser, setLoading, setError, fetchAllData }) => ({       user,       setUser,       setLoading,       setError,       fetchAllData     })   )    useEffect(() => {     \/\/ \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u0440\u0438 \u0438\u0445 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0438     if (!user) {       setLoading(true)       userApi         .get()         .then((user) => {           \/\/ \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f           \/\/ `user` \u043c\u043e\u0436\u0435\u0442 \u0438\u043c\u0435\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 `null`           setUser(user)           \/\/ \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u0432\u0441\u0435\u0445 \u0442\u0430\u0431\u043b\u0438\u0446           fetchAllData()         })         .catch(setError)     }   }, [])    return (     &lt;div className='app'>       &lt;header>         &lt;Nav \/>       &lt;\/header>       &lt;main>         &lt;Layout>           &lt;Routes>             &lt;Route path='\/' element={&lt;Home \/>} \/>             &lt;Route path='\/blog' element={&lt;Blog \/>} \/>             &lt;Route path='\/blog\/post\/:id' element={&lt;Post \/>} \/>             &lt;Route path='\/about' element={&lt;About \/>} \/>             &lt;Route path='\/register' element={&lt;Register \/>} \/>             &lt;Route path='\/login' element={&lt;Login \/>} \/>             &lt;Route path='\/profile' element={&lt;Profile \/>} \/>           &lt;\/Routes>         &lt;\/Layout>       &lt;\/main>       &lt;footer>         &lt;p>&amp;copy; 2022. Not all rights reserved&lt;\/p>       &lt;\/footer>     &lt;\/div>   ) }  export default App<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u043c\u044b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043b\u0438 \u0432\u0441\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<p>  <\/p>\n<h2 id=\"proverka-rabotosposobnosti\">\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0440\u0430\u0431\u043e\u0442\u043e\u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u0438<\/h2>\n<p>  <\/p>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0443\u0431\u0435\u0434\u0438\u043c\u0441\u044f \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u043a\u0430\u043a \u043e\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044f.<\/p>\n<p>  <\/p>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0434\u043b\u044f \u043d\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0431\u0430\u0437\u044b \u0444\u0438\u043a\u0442\u0438\u0432\u043d\u044b\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438. \u0412 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 <code>src<\/code> \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0444\u0430\u0439\u043b <code>seedDb.js<\/code> \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">import { createClient } from '@supabase\/supabase-js' import serializeUser from '..\/utils\/serializeUser.js' import { config } from 'dotenv'  \/\/ \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u043c \u0441\u0440\u0435\u0434\u044b \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f config()  \/\/ \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043a\u043b\u0438\u0435\u043d\u0442\u0430 `Supabase` const supabase = createClient(   process.env.VITE_SUPABASE_URL,   process.env.VITE_SUPABASE_KEY )  \/\/ \u0441\u043e\u0437\u0434\u0430\u0435\u043c 2 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439, `Alice` \u0438 `Bob` \u0441 \u043f\u043e\u0441\u0442\u0430\u043c\u0438 \u0438 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u043c\u0438 async function seedDb() {   try {     const { user: aliceAuth } = await supabase.auth.signUp(       {         email: 'alice@mail.com',         password: 'password'       },       {         data: {           user_name: 'Alice'         }       }     )     const { user: bobAuth } = await supabase.auth.signUp(       {         email: 'bob@mail.com',         password: 'password'       },       {         data: {           user_name: 'Bob'         }       }     )     const {       data: [alice, bob]     } = await supabase       .from('users')       .insert([serializeUser(aliceAuth), serializeUser(bobAuth)])      const { data: alicePosts } = await supabase.from('posts').insert([       {         title: `Alice's first post`,         content: `This is Alice's first post`,         user_id: alice.id       },       {         title: `Alice's second post`,         content: `This is Alice's second post`,         user_id: alice.id       }     ])     const { data: bobPosts } = await supabase.from('posts').insert([       {         title: `Bob's's first post`,         content: `This is Bob's first post`,         user_id: bob.id       },       {         title: `Bob's's second post`,         content: `This is Bob's second post`,         user_id: bob.id       }     ])     for (const post of alicePosts) {       await supabase.from('comments').insert([         {           user_id: alice.id,           post_id: post.id,           content: `This is Alice's comment on Alice's post \"${post.title}\"`         },         {           user_id: bob.id,           post_id: post.id,           content: `This is Bob's comment on Alice's post \"${post.title}\"`         }       ])     }     for (const post of bobPosts) {       await supabase.from('comments').insert([         {           user_id: alice.id,           post_id: post.id,           content: `This is Alice's comment on Bob's post \"${post.title}\"`         },         {           user_id: bob.id,           post_id: post.id,           content: `This is Bob's comment on Bob's post \"${post.title}\"`         }       ])     }     console.log('Done')   } catch (e) {     console.error(e)   } } seedDb()<\/code><\/pre>\n<p>  <\/p>\n<p>\u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u044d\u0442\u043e\u0442 \u043a\u043e\u0434 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <code>node src\/seed_db.js<\/code>. \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0431 \u0443\u0441\u043f\u0435\u0445\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 (<code>Done<\/code>). \u0412 \u0411\u0414 \u043f\u043e\u044f\u0432\u0438\u043b\u043e\u0441\u044c 2 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, 4 \u043f\u043e\u0441\u0442\u0430 \u0438 8 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432.<\/p>\n<p>  <\/p>\n<p>\u041d\u0430\u0445\u043e\u0434\u044f\u0441\u044c \u0432 \u043a\u043e\u0440\u043d\u0435\u0432\u043e\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 (<code>supabase-social-app<\/code>), \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043a\u043e\u043c\u0430\u043d\u0434\u0443 <code>yarn dev<\/code> \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438.<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/rz\/gx\/se\/rzgxsedpo8chvapson3m_aqjhnm.png\" data-src=\"https:\/\/habrastorage.org\/webt\/rz\/gx\/se\/rzgxsedpo8chvapson3m_aqjhnm.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 (<code>Register<\/code>) \u0438 \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. <em>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435<\/em>: <code>Supabase<\/code> \u0442\u0440\u0435\u0431\u0443\u0435\u0442, \u0447\u0442\u043e\u0431\u044b \u043f\u0430\u0440\u043e\u043b\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043b \u043a\u0430\u043a \u043c\u0438\u043d\u0438\u043c\u0443\u043c \u0438\u0437 6 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432.<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/si\/9p\/hz\/si9phzhpguhvkfyl6casfkl8dt0.png\" data-src=\"https:\/\/habrastorage.org\/webt\/si\/9p\/hz\/si9phzhpguhvkfyl6casfkl8dt0.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/iv\/5u\/-x\/iv5u-xvbr6mpqawnlhfp3s0nupa.png\" data-src=\"https:\/\/habrastorage.org\/webt\/iv\/5u\/-x\/iv5u-xvbr6mpqawnlhfp3s0nupa.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041d\u0430 \u043f\u0430\u043d\u0435\u043b\u0438 \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0438 \u043f\u043e\u044f\u0432\u0438\u043b\u0430\u0441\u044c \u043a\u043d\u043e\u043f\u043a\u0430 \u0434\u043b\u044f \u0432\u044b\u0445\u043e\u0434\u0430 \u0438\u0437 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0438 \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u043f\u0440\u043e\u0444\u0438\u043b\u044f.<\/p>\n<p>  <\/p>\n<p>\u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u043f\u0440\u043e\u0444\u0438\u043b\u044f (<code>Profile<\/code>), \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u0430\u0432\u0430\u0442\u0430\u0440 \u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435.<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/hz\/6a\/fe\/hz6afebw09el8jylqdt0xocjw4s.png\" data-src=\"https:\/\/habrastorage.org\/webt\/hz\/6a\/fe\/hz6afebw09el8jylqdt0xocjw4s.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/4l\/8y\/bf\/4l8ybf0rh91j9852mbvur-01wb8.png\" data-src=\"https:\/\/habrastorage.org\/webt\/4l\/8y\/bf\/4l8ybf0rh91j9852mbvur-01wb8.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u0412\u043c\u0435\u0441\u0442\u043e \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u043f\u0440\u043e\u0444\u0438\u043b\u044f \u0443 \u043d\u0430\u0441 \u0442\u0435\u043f\u0435\u0440\u044c \u0438\u043c\u0435\u0435\u0442\u0441\u044f \u0430\u0432\u0430\u0442\u0430\u0440 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u0430 \u0432 \u043e\u0431\u044a\u0435\u043a\u0442\u0435 <code>user<\/code> \u2014 \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044f <code>first_name<\/code>, <code>last_name<\/code> \u0438 <code>age<\/code>.<\/p>\n<p>  <\/p>\n<p>\u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0431\u043b\u043e\u0433\u0430 (<code>Blog<\/code>), &#171;\u043f\u0440\u043e\u0432\u0430\u043b\u0438\u0432\u0430\u0435\u043c\u0441\u044f&#187; \u0432 \u043a\u0430\u043a\u043e\u0439-\u043d\u0438\u0431\u0443\u0434\u044c \u043f\u043e\u0441\u0442 \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043a \u043d\u0435\u043c\u0443 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439.<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/vk\/0w\/rv\/vk0wrvizoh_zypvw05seuqnx9hw.png\" data-src=\"https:\/\/habrastorage.org\/webt\/vk\/0w\/rv\/vk0wrvizoh_zypvw05seuqnx9hw.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/h2\/tc\/2z\/h2tc2zwnne6pvnvnu2_awbe1y0e.png\" data-src=\"https:\/\/habrastorage.org\/webt\/h2\/tc\/2z\/h2tc2zwnne6pvnvnu2_awbe1y0e.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/ol\/at\/bb\/olatbbuqjx8oqedcluskylesd8w.png\" data-src=\"https:\/\/habrastorage.org\/webt\/ol\/at\/bb\/olatbbuqjx8oqedcluskylesd8w.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0439 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439 \u043c\u043e\u0436\u043d\u043e \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u0443\u0434\u0430\u043b\u044f\u0442\u044c.<\/p>\n<p>  <\/p>\n<p>\u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u0441\u044f \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0431\u043b\u043e\u0433\u0430, \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0435\u043c\u0441\u044f \u043d\u0430 \u0432\u043a\u043b\u0430\u0434\u043a\u0443 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043d\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u0441\u0442\u0430 (<code>New<\/code>) \u0438 \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0435\u0433\u043e.<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/em\/dm\/ku\/emdmkufq97axx4klhnrrdhyqdyg.png\" data-src=\"https:\/\/habrastorage.org\/webt\/em\/dm\/ku\/emdmkufq97axx4klhnrrdhyqdyg.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/oy\/pk\/ei\/oypkeijn4xvinpm9gtbazbuters.png\" data-src=\"https:\/\/habrastorage.org\/webt\/oy\/pk\/ei\/oypkeijn4xvinpm9gtbazbuters.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041d\u0430 \u0432\u043a\u043b\u0430\u0434\u043a\u0435 <code>My<\/code> \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0431\u043b\u043e\u0433\u0430 \u043c\u043e\u0436\u043d\u043e \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u0432\u0441\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0430\u043c\u0438 \u043f\u043e\u0441\u0442\u044b.<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/jy\/c9\/pe\/jyc9pewh3hasdn9bpaia4unohj8.png\" data-src=\"https:\/\/habrastorage.org\/webt\/jy\/c9\/pe\/jyc9pewh3hasdn9bpaia4unohj8.png\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u0418\u0445 \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u043d\u043e \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u0443\u0434\u0430\u043b\u044f\u0442\u044c.<\/p>\n<p>  <\/p>\n<p>\u041a\u0440\u0443\u0442\u043e! \u0412\u0441\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u043a\u0430\u043a \u0447\u0430\u0441\u044b.<\/p>\n<p>  <\/p>\n<p>\u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, <code>Supabase<\/code> \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u043c \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043f\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044e <code>fullstack app<\/code>, \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044f \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0441\u043e\u0441\u0440\u0435\u0434\u043e\u0442\u043e\u0447\u0438\u0442\u044c\u0441\u044f \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. So give it a chance!<\/p>\n<p>  <\/p>\n<p>\u041f\u043e\u0436\u0430\u043b\u0443\u0439, \u044d\u0442\u043e \u0432\u0441\u0435, \u0447\u0435\u043c \u044f \u0445\u043e\u0442\u0435\u043b \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0441 \u0432\u0430\u043c\u0438 \u0432 \u0434\u0430\u043d\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435.<\/p>\n<p>  <\/p>\n<p>\u0411\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044e \u0437\u0430 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u0438 happy coding!<\/p>\n<p>  <\/p>\n<hr\/>\n<p>  <\/p>\n<p><a href=\"http:\/\/cloud.timeweb.com\/services\/timeweb-private-vpn?utm_source=habr&amp;utm_medium=banner&amp;utm_campaign=vpn\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/ky\/mc\/4w\/kymc4w0tsvxjf9ninsv-gqhcpx0.png\" data-src=\"https:\/\/habrastorage.org\/webt\/ky\/mc\/4w\/kymc4w0tsvxjf9ninsv-gqhcpx0.png\"\/><\/a><\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/660183\/\"> https:\/\/habr.com\/ru\/articles\/660183\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/webt\/pw\/hr\/ps\/pwhrpsl3_b_b80p3rzkdgc5yu_c.jpeg\" data-src=\"https:\/\/habrastorage.org\/webt\/pw\/hr\/ps\/pwhrpsl3_b_b80p3rzkdgc5yu_c.jpeg\" data-blurred=\"true\"\/>  <\/p>\n<p>  <\/p>\n<p>  <\/p>\n<p>\u041f\u0440\u0438\u0432\u0435\u0442, \u0434\u0440\u0443\u0437\u044c\u044f!<\/p>\n<p>  <\/p>\n<p>\u0412 \u044d\u0442\u043e\u043c \u0446\u0438\u043a\u043b\u0435 \u0438\u0437 2 \u0441\u0442\u0430\u0442\u0435\u0439 \u044f \u0445\u043e\u0447\u0443 \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u0442\u044c \u0432\u0430\u043c \u043e <a href=\"https:\/\/supabase.com\/\">Supabase<\/a> \u2014 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u0439 (open source), \u0442.\u0435. \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u043e\u0439 \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0435 <a href=\"https:\/\/firebase.google.com\/\">Firebase<\/a>. \u0412 <a href=\"https:\/\/habr.com\/ru\/company\/timeweb\/blog\/648761\/\">\u043f\u0435\u0440\u0432\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435<\/a> \u043c\u044b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043b\u0438 \u0442\u0435\u043e\u0440\u0438\u044e, \u0432 \u044d\u0442\u043e\u0439 \u2014 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u043e\u0435 <code>social app<\/code> \u0441 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0435\u0439, \u0431\u0430\u0437\u043e\u0439 \u0434\u0430\u043d\u043d\u044b\u0445, \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435\u043c \u0444\u0430\u0439\u043b\u043e\u0432 \u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u043e\u0439 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438.<\/p>\n<p>  <\/p>\n<p><a href=\"https:\/\/github.com\/harryheman\/Blog-Posts\/tree\/master\/supabase-social-app\">\u0420\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0441 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u043c \u043a\u043e\u0434\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432\u0430\u043c \u044d\u0442\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e, \u043f\u0440\u043e\u0448\u0443 \u043f\u043e\u0434 \u043a\u0430\u0442.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-385548","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/385548","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=385548"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/385548\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=385548"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=385548"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=385548"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}