├── .gitignore ├── README.md ├── jsconfig.json ├── package-lock.json ├── package.json ├── postcss.config.cjs ├── src ├── app.css ├── app.html ├── global.d.ts ├── lib │ ├── CreatePost.svelte │ ├── Cubed.svelte │ ├── Error.svelte │ ├── Post.svelte │ ├── UploadImage.svelte │ ├── services.js │ └── supabase.js └── routes │ ├── __layout.svelte │ ├── index.svelte │ └── login.svelte ├── static └── favicon.png ├── svelte.config.js └── tailwind.config.cjs /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Qwitter 2 | 3 | ## Disclaimer 4 | I didn't finish the README but please reference the video! 5 | [Video](https://www.youtube.com/watch?v=mPQyckogDYc) 6 | 7 | ## Made with 8 | - Sveltekit 9 | - Supabase 10 | - DaisyUI (TailwindCSS) 11 | ## Todo 12 | ### A. Setup Sveltekit project 13 | 1. `$ npm init svelte@next` (for prompt: Skeleton, no, no, no) 14 | 2. `$ npx svelte-add@latest tailwindcss` 15 | 3. `$ npm install` 16 | 4. `$ npm install daisyui` 17 | 5. Add `require('daisyui')` to `plugins` in `tailwind.config.cjs` 18 | (like: `plugins: [require('daisyui')],`) 19 | ### B. Setup Supabase project 20 | 1. Create new Project 21 | 2. Add src/lib/supabase.js file and add the supabase url and public key 22 | ``` 23 | import { createClient } from '@supabase/supabase-js' 24 | 25 | const SUPABSE_URL = '' 26 | const SUPABASE_PUBLIC_KEY = '' 27 | 28 | const supabase = createClient(SUPABSE_URL, SUPABASE_PUBLIC_KEY) 29 | export default supabase 30 | ``` 31 | 32 | ### C. Some Random Things.. 33 | 1. (Optional) Disable need to Confirm Email under Authentication > Settings 34 | 2. (Optional) [Choose a theme](https://daisyui.com/docs/default-themes) and add it to src/app.html like `` 35 | 3. Add the following to your supabase.js file 36 | ``` 37 | import {goto} from '$app/navigation' 38 | supabase.auth.onAuthStateChange((event) => { 39 | if (event === 'SIGNED_IN') { 40 | goto('/') 41 | } else if (event === 'SIGNED_OUT') { 42 | goto('/login') 43 | } 44 | }) 45 | ``` 46 | 47 | 4. Add a src/lib/Error.svelte component 48 | ``` 49 | 52 | 53 | {#if error} 54 | {error.message} 55 | {/if} 56 | ``` 57 | 58 | 4. Add a main.container to src/routes/__layout.svelte 59 | ``` 60 |
61 | 62 |
63 | ``` 64 | 65 | ### D. Setup Login Page 66 | 1. Add a src/lib/services.js file 67 | ``` 68 | import supabase from './supabase' 69 | 70 | export function getUser() { 71 | return supabase.auth.user() 72 | } 73 | 74 | export async function signIn({email}) { 75 | const {error} = await supabase.auth 76 | .signIn({email}) 77 | return {data: !error, error} 78 | } 79 | 80 | export async function signOut() { 81 | const {error} = await supabase.auth 82 | .signOut() 83 | return {data: !error, error} 84 | } 85 | ``` 86 | 2. Add a src/routes/login.svelte page 87 | ``` 88 | 97 | ``` 98 | 3. Use an [Input with Button](https://daisyui.com/components/form/input) (scroll down) for the Magic Link 99 | 4. Add in form/promise logic to handle the signin (See video or login.svelte file) 100 | 101 | ### E. Add CreatePost.svelte 102 | 1. Create posts schema in Supabase (user, content) 103 | 2. Add to services.js 104 | ``` 105 | export async function createPost({content, user}) { // user is user's email 106 | const {data, error} = await supabase 107 | .from('posts') 108 | .insert({content, user}) 109 | return {data, error} 110 | } 111 | ``` 112 | 3. Add src/lib/CreatePost.svelte (See video or CreatePost.svelte) 113 | 114 | ### F. Add Post.svelte 115 | 1. Add to services.js 116 | ``` 117 | export async function createLike({post, user}) { // post is post's id 118 | const {data, error} = await supabase 119 | .from('likes') 120 | .insert({post, user}) 121 | return {data, error} 122 | } 123 | 124 | export async function createComment({post, user, content}) { 125 | const {data, error} = await supabase 126 | .from('comments') 127 | .insert({post, user, content}) 128 | return {data, error} 129 | } 130 | ``` 131 | 2. Add src/lib/Post.svelte (See video or Post.svelte) 132 | 3. Use a [Card without Image](https://daisyui.com/components/card) (scroll down) 133 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "$lib": ["src/lib"], 6 | "$lib/*": ["src/lib/*"] 7 | } 8 | }, 9 | "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"] 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qwitter", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "dev": "svelte-kit dev", 6 | "build": "svelte-kit build", 7 | "package": "svelte-kit package", 8 | "preview": "svelte-kit preview" 9 | }, 10 | "devDependencies": { 11 | "@sveltejs/adapter-auto": "next", 12 | "@sveltejs/kit": "next", 13 | "autoprefixer": "^10.3.7", 14 | "cssnano": "^5.0.8", 15 | "postcss": "^8.3.9", 16 | "postcss-load-config": "^3.1.0", 17 | "svelte": "^3.44.0", 18 | "svelte-preprocess": "^4.9.8", 19 | "tailwindcss": "^2.2.16" 20 | }, 21 | "type": "module", 22 | "dependencies": { 23 | "@supabase/supabase-js": "^1.28.1", 24 | "daisyui": "^1.16.2", 25 | "svelte-cubed": "^0.2.1", 26 | "three": "^0.134.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const tailwindcss = require("tailwindcss"); 2 | const autoprefixer = require("autoprefixer"); 3 | const cssnano = require("cssnano"); 4 | 5 | const mode = process.env.NODE_ENV; 6 | const dev = mode === "development"; 7 | 8 | const config = { 9 | plugins: [ 10 | //Some plugins, like tailwindcss/nesting, need to run before Tailwind, 11 | tailwindcss(), 12 | //But others, like autoprefixer, need to run after, 13 | autoprefixer(), 14 | !dev && 15 | cssnano({ 16 | preset: "default", 17 | }), 18 | ], 19 | }; 20 | 21 | module.exports = config; 22 | -------------------------------------------------------------------------------- /src/app.css: -------------------------------------------------------------------------------- 1 | /* Write your global styles here, in PostCSS syntax */ 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | %svelte.head% 9 | 10 | 11 |
%svelte.body%
12 | 13 | 14 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/lib/CreatePost.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |
16 | 19 | 20 | 21 | {#await createPostPromise} 22 | 23 | {:then {data, error}} 24 | 25 | {#if data} 26 | Successfully create post! 27 | {/if} 28 | 29 | {/await} 30 | 31 | -------------------------------------------------------------------------------- /src/lib/Cubed.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 20 | 21 | {#if showMessage} 22 |

Cool, huh? Now sign up!!!

23 | {/if} -------------------------------------------------------------------------------- /src/lib/Error.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | {#if error} 6 | {error.message} 7 | {/if} -------------------------------------------------------------------------------- /src/lib/Post.svelte: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 | {#if publicURL} 27 |
28 | 29 |
30 | {/if} 31 |
32 |

{user} says..

33 |

{content}

34 |
35 | {#await createCommentPromise} 36 | Posting comment.. 37 | {:then {data, error}} 38 | 39 | {#if data} 40 | Thanks for creating an insightful and kind comment! 41 | {/if} 42 |
43 |
44 | 45 | 46 |
47 |
48 | {/await} 49 | 50 |
51 | {#each comments as comment} 52 | {comment.user} says.. {comment.content} 53 | {/each} 54 |
55 |
-------------------------------------------------------------------------------- /src/lib/UploadImage.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | -------------------------------------------------------------------------------- /src/lib/services.js: -------------------------------------------------------------------------------- 1 | import supabase from './supabase' 2 | 3 | export function getUser() { 4 | return supabase.auth.user() 5 | } 6 | 7 | export async function signIn({email}) { 8 | const {error} = await supabase.auth 9 | .signIn({email}) 10 | return {data: !error, error} 11 | } 12 | 13 | export async function signOut() { 14 | const {error} = await supabase.auth 15 | .signOut() 16 | return {data: !error, error} 17 | } 18 | 19 | export async function createPost({content, user, imageFile}) { // user is user's email 20 | if (imageFile) { 21 | const { data: imageData, error: imageError } = await supabase 22 | .storage 23 | .from('images') 24 | .upload(getUser().email + '/' + Date.now(), imageFile, { 25 | cacheControl: '3600', 26 | upsert: false 27 | }) 28 | if (imageError) return {data: null, error: imageError} 29 | const {data, error} = await supabase 30 | .from('posts') 31 | .insert({content, user, image: imageData.Key}) 32 | 33 | return {data, error} 34 | } else { 35 | 36 | const {data, error} = await supabase 37 | .from('posts') 38 | .insert({content, user}) 39 | return {data, error} 40 | } 41 | } 42 | 43 | 44 | export async function createLike({post, user}) { // post is post's id 45 | const {data, error} = await supabase 46 | .from('likes') 47 | .insert({post, user}) 48 | return {data, error} 49 | } 50 | 51 | export async function createComment({post, user, content}) { 52 | const {data, error} = await supabase 53 | .from('comments') 54 | .insert({post, user, content}) 55 | return {data, error} 56 | } 57 | 58 | export async function getPosts() { 59 | let {data, error} = await supabase 60 | .from('posts') 61 | .select('*') 62 | .order('created_at', {ascending: false}) 63 | .limit(5) 64 | 65 | if (error) return {data, error} 66 | 67 | data = await Promise.all(data.map(async (post) => { 68 | const [{count: likes,}, {data: comments, }, {publicURL}] = await Promise.all([ 69 | await supabase 70 | .from('likes') 71 | .select('id', { count: 'estimated', head: true }) 72 | .eq('post', post.id), 73 | await supabase 74 | .from('comments') 75 | .select('*') 76 | .eq('post', post.id), 77 | post.image ? await supabase.storage.from('images').getPublicUrl(post.image.split('/').slice(1).join('/')) : Promise.resolve({}) 78 | ]) 79 | // ERROR HANDLE!!! 80 | return { 81 | ...post, likes, comments, publicURL 82 | } 83 | })) 84 | return {data, error} 85 | } -------------------------------------------------------------------------------- /src/lib/supabase.js: -------------------------------------------------------------------------------- 1 | import { createClient } from '@supabase/supabase-js' 2 | 3 | const SUPABSE_URL = 'https://sxkcwshulbsvwnlvjgfw.supabase.co' 4 | const SUPABASE_PUBLIC_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYzNzg3NzEzMSwiZXhwIjoxOTUzNDUzMTMxfQ.GHOELGasMrXrPyf-8ATREq9aR64tAb0J06Nz_SIOQAs' 5 | 6 | const supabase = createClient(SUPABSE_URL, SUPABASE_PUBLIC_KEY) 7 | 8 | export default supabase -------------------------------------------------------------------------------- /src/routes/__layout.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 7 |
8 | -------------------------------------------------------------------------------- /src/routes/index.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 33 | 34 | 35 |
36 |

Welcome to Qwitter!

37 | 38 |
39 | 40 | 41 | 42 | {#each posts || [] as post} 43 | 44 | {:else} 45 | No posts found! 46 | {/each} -------------------------------------------------------------------------------- /src/routes/login.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 | {#await signInPromise} 21 | Sending magic link to {email} 22 | {:then {data, error}} 23 | 24 | {#if data} 25 | Successfully sent magic link to {email}! 26 | {:else} 27 |
28 | 31 |
32 | 33 | 34 |
35 |
36 | {/if} 37 | {/await} 38 | 39 | -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sveltemaster/qwitter/1004730d2fdd9780754f87d7fa65468e2ce4a081/static/favicon.png -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import preprocess from "svelte-preprocess"; 2 | import adapter from "@sveltejs/adapter-auto"; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | kit: { 7 | adapter: adapter(), 8 | 9 | // hydrate the
element in src/app.html 10 | target: "#svelte", 11 | ssr: false 12 | }, 13 | 14 | preprocess: [ 15 | preprocess({ 16 | postcss: true, 17 | }), 18 | ], 19 | }; 20 | 21 | export default config; 22 | -------------------------------------------------------------------------------- /tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | mode: "jit", 3 | purge: ["./src/**/*.{html,js,svelte,ts}"], 4 | 5 | theme: { 6 | extend: {}, 7 | }, 8 | 9 | plugins: [ 10 | require('daisyui') 11 | ], 12 | }; 13 | 14 | module.exports = config; 15 | --------------------------------------------------------------------------------