diff --git a/web/docs/about.mdx b/web/docs/about.mdx index 873e3cdb62d..a34daac63d2 100755 --- a/web/docs/about.mdx +++ b/web/docs/about.mdx @@ -14,6 +14,7 @@ import JavascriptLogo from '@site/static/img/libraries/javascript-icon.svg' import NextjsDarkLogo from '@site/static/img/libraries/nextjs-dark-icon.svg' import NextjsLightLogo from '@site/static/img/libraries/nextjs-light-icon.svg' import ReactLogo from '@site/static/img/libraries/react-icon.svg' +import SolidJSLogo from '@site/static/img/libraries/solidjs-icon.svg' import RedwoodJsLogo from '@site/static/img/libraries/redwoodjs-icon.svg' import SvelteLogo from '@site/static/img/libraries/svelte-icon.svg' import VuejsLogo from '@site/static/img/libraries/vuejs-icon.svg' @@ -50,6 +51,7 @@ const frameworks = [ }, { name: 'React', logo: ReactLogo, href: '/docs/guides/with-react' }, { name: 'RedwoodJS', logo: RedwoodJsLogo, href: '/docs/guides/with-redwoodjs' }, + { name: 'SolidJS', logo: SolidJSLogo, href: '/docs/guides/with-solidjs' }, { name: 'Svelte', logo: SvelteLogo, href: '/docs/guides/with-svelte' }, { name: 'Vue', logo: VuejsLogo, href: '/docs/guides/with-vue-3' }, ] diff --git a/web/docs/guides/with-solidjs.mdx b/web/docs/guides/with-solidjs.mdx new file mode 100644 index 00000000000..8c0c1f969a9 --- /dev/null +++ b/web/docs/guides/with-solidjs.mdx @@ -0,0 +1,541 @@ +--- +id: with-solidjs +title: 'Quickstart: SolidJS' +description: Learn how to use Supabase in your SolidJS App. +--- + +import Tabs from '@theme/Tabs' +import TabItem from '@theme/TabItem' + +## Intro + +This example provides the steps to build a simple user management app (from scratch!) using Supabase and Solid JS. It includes: + +- Supabase [Database](/docs/guides/database): a Postgres database for storing your user data. +- Supabase [Auth](/docs/guides/auth): users can sign in with magic links (no passwords, only email). +- Supabase [Storage](/docs/guides/storage): users can upload a photo. +- [Row Level Security](/docs/guides/auth#row-level-security): data is protected so that individuals can only access their own data. +- Instant [APIs](/docs/guides/api): APIs will be automatically generated when you create your database tables. + +By the end of this guide you'll have an app which allows users to login and update some basic profile details: + +![Supabase User Management example](/img/user-management-demo.png) + + + +## Project set up + +Before we start building we're going to set up our Database and API. This is as simple as starting a new Project in Supabase +and then creating a "schema" inside the database. + +### Create a project + +1. Go to [app.supabase.io](https://app.supabase.io). +1. Click on "New Project". +1. Enter your project details. +1. Wait for the new database to launch. + +### Set up the database schema + +Now we are going to set up the database schema. We can use the "User Management Starter" quickstart in the SQL Editor, +or you can just copy/paste the SQL from below and run it yourself. + + + + +```sh +1. Go to the "SQL" section. +2. Click "User Management Starter". +3. Click "Run". +``` + + + + + + +```sql +-- Create a table for public "profiles" +create table profiles ( + id uuid references auth.users not null, + updated_at timestamp with time zone, + username text unique, + avatar_url text, + website text, + + primary key (id), + unique(username), + constraint username_length check (char_length(username) >= 3) +); + +alter table profiles enable row level security; + +create policy "Public profiles are viewable by everyone." + on profiles for select + using ( true ); + +create policy "Users can insert their own profile." + on profiles for insert + with check ( auth.uid() = id ); + +create policy "Users can update own profile." + on profiles for update + using ( auth.uid() = id ); + +-- Set up Realtime! +begin; + drop publication if exists supabase_realtime; + create publication supabase_realtime; +commit; +alter publication supabase_realtime add table profiles; + +-- Set up Storage! +insert into storage.buckets (id, name) +values ('avatars', 'avatars'); + +create policy "Avatar images are publicly accessible." + on storage.objects for select + using ( bucket_id = 'avatars' ); + +create policy "Anyone can upload an avatar." + on storage.objects for insert + with check ( bucket_id = 'avatars' ); + +``` + + + + +### Get the API Keys + +Now that you've created some database tables, you are ready to insert data using the auto-generated API. +We just need to get the URL and `anon` key from the API settings. + + + + +```sh +1. Go to the "Settings" section. +2. Click "API" in the sidebar. +3. Find your API URL in this page. +4. Find your "anon" and "service_role" keys on this page. +``` + + + + + + +## Building the App + +Let's start building the SolidJS app from scratch. + +### Initialize a SolidJS app + +We can use [Degit](https://github.com/Rich-Harris/degit) to initialize an app called `supabase-solid`: + +```bash +npx degit solidjs/templates/js supabase-solid +cd supabase-solid +``` + +Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js) + +```bash +npm install @supabase/supabase-js +``` + +And finally we want to save the environment variables in a `.env`. +All we need are the API URL and the `anon` key that you copied [earlier](#get-the-api-keys). + +```bash title=".env" +VITE_SUPABASE_URL=YOUR_SUPABASE_URL +VITE_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY +``` + +Now that we have the API credentials in place, let's create a helper file to initialize the Supabase client. These variables will be exposed +on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database. + +```js title="src/supabaseClient.jsx" +import { createClient } from '@supabase/supabase-js' + +const supabaseUrl = process.env.VITE_SUPABASE_URL +const supabaseAnonKey = process.env.VITE_SUPABASE_ANON_KEY + +export const supabase = createClient(supabaseUrl, supabaseAnonKey) +``` + +And one optional step is to update the CSS file `src/index.css` to make the app look nice. +You can find the full contents of this file [here](https://raw.githubusercontent.com/supabase/supabase/master/examples/react-user-management/src/index.css). + +### Set up a Login component + +Let's set up a SolidJS component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords. + +```jsx title="/src/Auth.jsx" +import { createSignal } from 'solid-js' +import { supabase } from './supabaseClient' + +export default function Auth() { + const [loading, setLoading] = createSignal(false) + const [email, setEmail] = createSignal('') + + const handleLogin = async (e) => { + e.preventDefault() + + try { + setLoading(true) + const { error } = await supabase.auth.signIn({ email: email() }) + if (error) throw error + alert('Check your email for the login link!') + } catch (error) { + alert(error.error_description || error.message) + } finally { + setLoading(false) + } + } + + return ( +
+
+

Supabase + SolidJS

+

Sign in via magic link with your email below

+ {loading() ? ( + 'Sending magic link...' + ) : ( +
+ + setEmail(e.target.value)} + /> + +
+ )} +
+
+ ) +} +``` + +### Account page + +After a user is signed in we can allow them to edit their profile details and manage their account. + +Let's create a new component for that called `Account.js`. + +```jsx title="src/Account.jsx" +import { createSignal, createEffect } from 'solid-js' +import { supabase } from './supabaseClient' + +const Account = (props) => { + const [loading, setLoading] = createSignal(true) + const [username, setUsername] = createSignal(null) + const [website, setWebsite] = createSignal(null) + const [avatar_url, setAvatarUrl] = createSignal(null) + + createEffect(() => { + props.session + getProfile() + }) + + const getProfile = async () => { + try { + setLoading(true) + const user = supabase.auth.user() + + let { data, error, status } = await supabase + .from('profiles') + .select(`username, website, avatar_url`) + .eq('id', user.id) + .single() + + if (error && status !== 406) { + throw error + } + + if (data) { + setUsername(data.username) + setWebsite(data.website) + setAvatarUrl(data.avatar_url) + } + } catch (error) { + alert(error.message) + } finally { + setLoading(false) + } + } + + const updateProfile = async (e) => { + e.preventDefault() + + try { + setLoading(true) + const user = supabase.auth.user() + + const updates = { + id: user.id, + username: username(), + website: website(), + avatar_url: avatar_url(), + updated_at: new Date(), + } + + let { error } = await supabase.from('profiles').upsert(updates, { + returning: 'minimal', // Don't return the value after inserting + }) + + if (error) { + throw error + } + } catch (error) { + alert(error.message) + } finally { + setLoading(false) + } + } + + return ( +
+ {loading() ? ( + 'Saving ...' + ) : ( +
+
Email: {props.session.user.email}
+
+ + setUsername(e.target.value)} + /> +
+
+ + setWebsite(e.target.value)} + /> +
+
+ +
+
+ )} + +
+ ) +} + +export default Account +``` + +### Launch! + +Now that we have all the components in place, let's update `App.js`: + +```jsx title="src/App.jsx" +import './index.css' +import { createSignal, createEffect } from 'solid-js' +import { supabase } from './supabaseClient' +import Auth from './Auth' +import Account from './Account' + +export default () => { + const [session, setSession] = createSignal(null) + + createEffect(() => { + setSession(supabase.auth.session()) + + supabase.auth.onAuthStateChange((_event, session) => { + setSession(session) + }) + }) + + return ( +
+ {!session() ? : } +
+ ) +} +``` + +Once that's done, run this in a terminal window: + +```bash +npm start +``` + +And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app. + +![Supabase SolidJS](/img/supabase-solidjs-demo.png) + +## Bonus: Profile photos + +Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like photos and videos. + +### Create an upload widget + +Let's create an avatar for the user so that they can upload a profile photo. We can start by creating a new component: + +```jsx title="src/Avatar.jsx" +import { createEffect, createSignal } from 'solid-js' +import { supabase } from './supabaseClient' +// import VisuallyHidden from '@reach/visually-hidden' + +export default (props) => { + const [avatarUrl, setAvatarUrl] = createSignal(null) + const [uploading, setUploading] = createSignal(false) + + createEffect(() => { + if (props.url) downloadImage(props.url) + }) + + const downloadImage = async (path) => { + try { + const { data, error } = await supabase.storage.from('avatars').download(path) + if (error) { + throw error + } + const url = URL.createObjectURL(data) + setAvatarUrl(url) + } catch (error) { + console.log('Error downloading image: ', error.message) + } + } + + const uploadAvatar = async (event) => { + try { + setUploading(true) + + if (!event.target.files || event.target.files.length === 0) { + throw new Error('You must select an image to upload.') + } + + const file = event.target.files[0] + const fileExt = file.name.split('.').pop() + const fileName = `${Math.random()}.${fileExt}` + const filePath = `${fileName}` + + let { error: uploadError } = await supabase.storage.from('avatars').upload(filePath, file) + + if (uploadError) { + throw uploadError + } + + props.onUpload(filePath) + } catch (error) { + alert(error.message) + } finally { + setUploading(false) + } + } + + return ( +
+ {avatarUrl() + {uploading() ? ( + 'Uploading...' + ) : ( + <> + + + + + + )} +
+ ) +} +``` + +### Add the new widget + +And then we can add the widget to the Account page: + +```jsx title="src/Account.jsx" +// Import the new component +import Avatar from './Avatar' + +// ... + +return ( +
+ {/* Add to the body */} + { + setAvatarUrl(url) + updateProfile({ username, website, avatar_url: url }) + }} + /> + {/* ... */} +
+) +``` + +## Next steps + +At this stage you have a fully functional application! + +- Got a question? [Ask here](https://github.com/supabase/supabase/discussions). +- Sign in: [app.supabase.io](https://app.supabase.io) diff --git a/web/sidebars.js b/web/sidebars.js index 889ed674fc5..919711c8540 100755 --- a/web/sidebars.js +++ b/web/sidebars.js @@ -63,6 +63,7 @@ module.exports = { 'guides/with-nextjs', 'guides/with-react', 'guides/with-redwoodjs', + 'guides/with-solidjs', 'guides/with-svelte', 'guides/with-vue-3', ], diff --git a/web/static/img/libraries/solidjs-icon.svg b/web/static/img/libraries/solidjs-icon.svg new file mode 100644 index 00000000000..1e60674bb81 --- /dev/null +++ b/web/static/img/libraries/solidjs-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/static/img/supabase-solidjs-demo.png b/web/static/img/supabase-solidjs-demo.png new file mode 100644 index 00000000000..1e2d075eb45 Binary files /dev/null and b/web/static/img/supabase-solidjs-demo.png differ