From 2bdf7fe627aefba3a5dee300b28ad7ed5709ff2d Mon Sep 17 00:00:00 2001 From: Aniket Singh <31aniket.singh@gmail.com> Date: Thu, 2 Jun 2022 17:05:27 +0530 Subject: [PATCH 01/57] removed .git file --- .../todo-list/flutter-todo-list/.gitignore | 46 ++ .../todo-list/flutter-todo-list/.metadata | 10 + .../todo-list/flutter-todo-list/README.md | 1 + .../flutter-todo-list/analysis_options.yaml | 29 + .../flutter-todo-list/android/.gitignore | 13 + .../android/app/build.gradle | 68 +++ .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 34 ++ .../com/example/todosupabase/MainActivity.kt | 6 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + .../flutter-todo-list/android/build.gradle | 31 ++ .../android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 6 + .../flutter-todo-list/android/settings.gradle | 11 + .../assets/supabase-dark.svg | 23 + .../flutter-todo-list/ios/.gitignore | 34 ++ .../ios/Flutter/AppFrameworkInfo.plist | 26 + .../ios/Flutter/Debug.xcconfig | 1 + .../ios/Flutter/Release.xcconfig | 1 + .../ios/Runner.xcodeproj/project.pbxproj | 481 +++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 87 +++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 +++++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 564 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 1588 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 1025 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 1716 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 1920 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 1895 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 3831 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 1888 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 3294 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 3612 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 ++ .../ios/Runner/Base.lproj/Main.storyboard | 26 + .../flutter-todo-list/ios/Runner/Info.plist | 47 ++ .../ios/Runner/Runner-Bridging-Header.h | 1 + .../flutter-todo-list/lib/constant.dart | 102 ++++ .../flutter-todo-list/lib/functions/auth.dart | 34 ++ .../flutter-todo-list/lib/functions/crud.dart | 92 ++++ .../todo-list/flutter-todo-list/lib/main.dart | 76 +++ .../flutter-todo-list/lib/model.dart | 12 + .../flutter-todo-list/lib/screens/signin.dart | 73 +++ .../flutter-todo-list/lib/screens/signup.dart | 63 +++ .../flutter-todo-list/lib/screens/todo.dart | 211 ++++++++ .../todo-list/flutter-todo-list/pubspec.lock | 509 ++++++++++++++++++ .../todo-list/flutter-todo-list/pubspec.yaml | 88 +++ .../flutter-todo-list/test/widget_test.dart | 30 ++ .../flutter-todo-list/web/favicon.png | Bin 0 -> 917 bytes .../flutter-todo-list/web/icons/Icon-192.png | Bin 0 -> 5292 bytes .../flutter-todo-list/web/icons/Icon-512.png | Bin 0 -> 8252 bytes .../web/icons/Icon-maskable-192.png | Bin 0 -> 5594 bytes .../web/icons/Icon-maskable-512.png | Bin 0 -> 20998 bytes .../flutter-todo-list/web/index.html | 104 ++++ .../flutter-todo-list/web/manifest.json | 35 ++ .../flutter-todo-list/windows/.gitignore | 17 + .../flutter-todo-list/windows/CMakeLists.txt | 95 ++++ .../windows/flutter/CMakeLists.txt | 103 ++++ .../flutter/generated_plugin_registrant.cc | 14 + .../flutter/generated_plugin_registrant.h | 15 + .../windows/flutter/generated_plugins.cmake | 16 + .../windows/runner/CMakeLists.txt | 17 + .../windows/runner/Runner.rc | 121 +++++ .../windows/runner/flutter_window.cpp | 61 +++ .../windows/runner/flutter_window.h | 33 ++ .../flutter-todo-list/windows/runner/main.cpp | 43 ++ .../windows/runner/resource.h | 16 + .../windows/runner/resources/app_icon.ico | Bin 0 -> 33772 bytes .../windows/runner/runner.exe.manifest | 20 + .../windows/runner/utils.cpp | 64 +++ .../flutter-todo-list/windows/runner/utils.h | 19 + .../windows/runner/win32_window.cpp | 245 +++++++++ .../windows/runner/win32_window.h | 98 ++++ 98 files changed, 3731 insertions(+) create mode 100644 examples/todo-list/flutter-todo-list/.gitignore create mode 100644 examples/todo-list/flutter-todo-list/.metadata create mode 100644 examples/todo-list/flutter-todo-list/README.md create mode 100644 examples/todo-list/flutter-todo-list/analysis_options.yaml create mode 100644 examples/todo-list/flutter-todo-list/android/.gitignore create mode 100644 examples/todo-list/flutter-todo-list/android/app/build.gradle create mode 100644 examples/todo-list/flutter-todo-list/android/app/src/debug/AndroidManifest.xml create mode 100644 examples/todo-list/flutter-todo-list/android/app/src/main/AndroidManifest.xml create mode 100644 examples/todo-list/flutter-todo-list/android/app/src/main/kotlin/com/example/todosupabase/MainActivity.kt create mode 100644 examples/todo-list/flutter-todo-list/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 examples/todo-list/flutter-todo-list/android/app/src/main/res/drawable/launch_background.xml create mode 100644 examples/todo-list/flutter-todo-list/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 examples/todo-list/flutter-todo-list/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 examples/todo-list/flutter-todo-list/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 examples/todo-list/flutter-todo-list/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 examples/todo-list/flutter-todo-list/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 examples/todo-list/flutter-todo-list/android/app/src/main/res/values-night/styles.xml create mode 100644 examples/todo-list/flutter-todo-list/android/app/src/main/res/values/styles.xml create mode 100644 examples/todo-list/flutter-todo-list/android/app/src/profile/AndroidManifest.xml create mode 100644 examples/todo-list/flutter-todo-list/android/build.gradle create mode 100644 examples/todo-list/flutter-todo-list/android/gradle.properties create mode 100644 examples/todo-list/flutter-todo-list/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 examples/todo-list/flutter-todo-list/android/settings.gradle create mode 100644 examples/todo-list/flutter-todo-list/assets/supabase-dark.svg create mode 100644 examples/todo-list/flutter-todo-list/ios/.gitignore create mode 100644 examples/todo-list/flutter-todo-list/ios/Flutter/AppFrameworkInfo.plist create mode 100644 examples/todo-list/flutter-todo-list/ios/Flutter/Debug.xcconfig create mode 100644 examples/todo-list/flutter-todo-list/ios/Flutter/Release.xcconfig create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner.xcodeproj/project.pbxproj create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/AppDelegate.swift create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Base.lproj/Main.storyboard create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Info.plist create mode 100644 examples/todo-list/flutter-todo-list/ios/Runner/Runner-Bridging-Header.h create mode 100644 examples/todo-list/flutter-todo-list/lib/constant.dart create mode 100644 examples/todo-list/flutter-todo-list/lib/functions/auth.dart create mode 100644 examples/todo-list/flutter-todo-list/lib/functions/crud.dart create mode 100644 examples/todo-list/flutter-todo-list/lib/main.dart create mode 100644 examples/todo-list/flutter-todo-list/lib/model.dart create mode 100644 examples/todo-list/flutter-todo-list/lib/screens/signin.dart create mode 100644 examples/todo-list/flutter-todo-list/lib/screens/signup.dart create mode 100644 examples/todo-list/flutter-todo-list/lib/screens/todo.dart create mode 100644 examples/todo-list/flutter-todo-list/pubspec.lock create mode 100644 examples/todo-list/flutter-todo-list/pubspec.yaml create mode 100644 examples/todo-list/flutter-todo-list/test/widget_test.dart create mode 100644 examples/todo-list/flutter-todo-list/web/favicon.png create mode 100644 examples/todo-list/flutter-todo-list/web/icons/Icon-192.png create mode 100644 examples/todo-list/flutter-todo-list/web/icons/Icon-512.png create mode 100644 examples/todo-list/flutter-todo-list/web/icons/Icon-maskable-192.png create mode 100644 examples/todo-list/flutter-todo-list/web/icons/Icon-maskable-512.png create mode 100644 examples/todo-list/flutter-todo-list/web/index.html create mode 100644 examples/todo-list/flutter-todo-list/web/manifest.json create mode 100644 examples/todo-list/flutter-todo-list/windows/.gitignore create mode 100644 examples/todo-list/flutter-todo-list/windows/CMakeLists.txt create mode 100644 examples/todo-list/flutter-todo-list/windows/flutter/CMakeLists.txt create mode 100644 examples/todo-list/flutter-todo-list/windows/flutter/generated_plugin_registrant.cc create mode 100644 examples/todo-list/flutter-todo-list/windows/flutter/generated_plugin_registrant.h create mode 100644 examples/todo-list/flutter-todo-list/windows/flutter/generated_plugins.cmake create mode 100644 examples/todo-list/flutter-todo-list/windows/runner/CMakeLists.txt create mode 100644 examples/todo-list/flutter-todo-list/windows/runner/Runner.rc create mode 100644 examples/todo-list/flutter-todo-list/windows/runner/flutter_window.cpp create mode 100644 examples/todo-list/flutter-todo-list/windows/runner/flutter_window.h create mode 100644 examples/todo-list/flutter-todo-list/windows/runner/main.cpp create mode 100644 examples/todo-list/flutter-todo-list/windows/runner/resource.h create mode 100644 examples/todo-list/flutter-todo-list/windows/runner/resources/app_icon.ico create mode 100644 examples/todo-list/flutter-todo-list/windows/runner/runner.exe.manifest create mode 100644 examples/todo-list/flutter-todo-list/windows/runner/utils.cpp create mode 100644 examples/todo-list/flutter-todo-list/windows/runner/utils.h create mode 100644 examples/todo-list/flutter-todo-list/windows/runner/win32_window.cpp create mode 100644 examples/todo-list/flutter-todo-list/windows/runner/win32_window.h diff --git a/examples/todo-list/flutter-todo-list/.gitignore b/examples/todo-list/flutter-todo-list/.gitignore new file mode 100644 index 00000000000..0fa6b675c0a --- /dev/null +++ b/examples/todo-list/flutter-todo-list/.gitignore @@ -0,0 +1,46 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/examples/todo-list/flutter-todo-list/.metadata b/examples/todo-list/flutter-todo-list/.metadata new file mode 100644 index 00000000000..5a023280af4 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 7e9793dee1b85a243edd0e06cb1658e98b077561 + channel: stable + +project_type: app diff --git a/examples/todo-list/flutter-todo-list/README.md b/examples/todo-list/flutter-todo-list/README.md new file mode 100644 index 00000000000..8f1af2000ca --- /dev/null +++ b/examples/todo-list/flutter-todo-list/README.md @@ -0,0 +1 @@ +A Flutter Supabase CRUD App diff --git a/examples/todo-list/flutter-todo-list/analysis_options.yaml b/examples/todo-list/flutter-todo-list/analysis_options.yaml new file mode 100644 index 00000000000..61b6c4de17c --- /dev/null +++ b/examples/todo-list/flutter-todo-list/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/examples/todo-list/flutter-todo-list/android/.gitignore b/examples/todo-list/flutter-todo-list/android/.gitignore new file mode 100644 index 00000000000..6f568019d3c --- /dev/null +++ b/examples/todo-list/flutter-todo-list/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/examples/todo-list/flutter-todo-list/android/app/build.gradle b/examples/todo-list/flutter-todo-list/android/app/build.gradle new file mode 100644 index 00000000000..7e58ab90520 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/android/app/build.gradle @@ -0,0 +1,68 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion flutter.compileSdkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.todosupabase" + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/examples/todo-list/flutter-todo-list/android/app/src/debug/AndroidManifest.xml b/examples/todo-list/flutter-todo-list/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000000..2b76131d6bb --- /dev/null +++ b/examples/todo-list/flutter-todo-list/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/examples/todo-list/flutter-todo-list/android/app/src/main/AndroidManifest.xml b/examples/todo-list/flutter-todo-list/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..d36513d9e95 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/examples/todo-list/flutter-todo-list/android/app/src/main/kotlin/com/example/todosupabase/MainActivity.kt b/examples/todo-list/flutter-todo-list/android/app/src/main/kotlin/com/example/todosupabase/MainActivity.kt new file mode 100644 index 00000000000..a97a4a1bbe3 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/android/app/src/main/kotlin/com/example/todosupabase/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.todosupabase + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/examples/todo-list/flutter-todo-list/android/app/src/main/res/drawable-v21/launch_background.xml b/examples/todo-list/flutter-todo-list/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000000..f74085f3f6a --- /dev/null +++ b/examples/todo-list/flutter-todo-list/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/examples/todo-list/flutter-todo-list/android/app/src/main/res/drawable/launch_background.xml b/examples/todo-list/flutter-todo-list/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000000..304732f8842 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/examples/todo-list/flutter-todo-list/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/todo-list/flutter-todo-list/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/examples/todo-list/flutter-todo-list/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/examples/todo-list/flutter-todo-list/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/examples/todo-list/flutter-todo-list/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/todo-list/flutter-todo-list/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/examples/todo-list/flutter-todo-list/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/examples/todo-list/flutter-todo-list/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/examples/todo-list/flutter-todo-list/android/app/src/main/res/values-night/styles.xml b/examples/todo-list/flutter-todo-list/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000000..3db14bb5391 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/examples/todo-list/flutter-todo-list/android/app/src/main/res/values/styles.xml b/examples/todo-list/flutter-todo-list/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000000..d460d1e9215 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/examples/todo-list/flutter-todo-list/android/app/src/profile/AndroidManifest.xml b/examples/todo-list/flutter-todo-list/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000000..2b76131d6bb --- /dev/null +++ b/examples/todo-list/flutter-todo-list/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/examples/todo-list/flutter-todo-list/android/build.gradle b/examples/todo-list/flutter-todo-list/android/build.gradle new file mode 100644 index 00000000000..4256f917362 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.6.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/examples/todo-list/flutter-todo-list/android/gradle.properties b/examples/todo-list/flutter-todo-list/android/gradle.properties new file mode 100644 index 00000000000..94adc3a3f97 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/examples/todo-list/flutter-todo-list/android/gradle/wrapper/gradle-wrapper.properties b/examples/todo-list/flutter-todo-list/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..bc6a58afdda --- /dev/null +++ b/examples/todo-list/flutter-todo-list/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip diff --git a/examples/todo-list/flutter-todo-list/android/settings.gradle b/examples/todo-list/flutter-todo-list/android/settings.gradle new file mode 100644 index 00000000000..44e62bcf06a --- /dev/null +++ b/examples/todo-list/flutter-todo-list/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/examples/todo-list/flutter-todo-list/assets/supabase-dark.svg b/examples/todo-list/flutter-todo-list/assets/supabase-dark.svg new file mode 100644 index 00000000000..9e96fab4ddc --- /dev/null +++ b/examples/todo-list/flutter-todo-list/assets/supabase-dark.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/todo-list/flutter-todo-list/ios/.gitignore b/examples/todo-list/flutter-todo-list/ios/.gitignore new file mode 100644 index 00000000000..7a7f9873ad7 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/examples/todo-list/flutter-todo-list/ios/Flutter/AppFrameworkInfo.plist b/examples/todo-list/flutter-todo-list/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000000..8d4492f977a --- /dev/null +++ b/examples/todo-list/flutter-todo-list/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 9.0 + + diff --git a/examples/todo-list/flutter-todo-list/ios/Flutter/Debug.xcconfig b/examples/todo-list/flutter-todo-list/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000000..592ceee85b8 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/examples/todo-list/flutter-todo-list/ios/Flutter/Release.xcconfig b/examples/todo-list/flutter-todo-list/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000000..592ceee85b8 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/examples/todo-list/flutter-todo-list/ios/Runner.xcodeproj/project.pbxproj b/examples/todo-list/flutter-todo-list/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000000..c699b229cca --- /dev/null +++ b/examples/todo-list/flutter-todo-list/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,481 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.todosupabase; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.todosupabase; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.todosupabase; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/examples/todo-list/flutter-todo-list/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/todo-list/flutter-todo-list/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000000..919434a6254 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/todo-list/flutter-todo-list/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/todo-list/flutter-todo-list/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000000..18d981003d6 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/examples/todo-list/flutter-todo-list/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/examples/todo-list/flutter-todo-list/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000000..f9b0d7c5ea1 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/examples/todo-list/flutter-todo-list/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/examples/todo-list/flutter-todo-list/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000000..c87d15a3352 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/todo-list/flutter-todo-list/ios/Runner.xcworkspace/contents.xcworkspacedata b/examples/todo-list/flutter-todo-list/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000000..1d526a16ed0 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/todo-list/flutter-todo-list/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/todo-list/flutter-todo-list/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000000..18d981003d6 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/examples/todo-list/flutter-todo-list/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/examples/todo-list/flutter-todo-list/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000000..f9b0d7c5ea1 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/examples/todo-list/flutter-todo-list/ios/Runner/AppDelegate.swift b/examples/todo-list/flutter-todo-list/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000000..70693e4a8c1 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000000..d36b1fab2d9 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 diff --git a/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f091b6b0bca859a3f474b03065bef75ba58a9e4c GIT binary patch literal 1588 zcmV-42Fv-0P)C1SqPt}wig>|5Crh^=oyX$BK<}M8eLU3e2hGT;=G|!_SP)7zNI6fqUMB=)y zRAZ>eDe#*r`yDAVgB_R*LB*MAc)8(b{g{9McCXW!lq7r(btRoB9!8B-#AI6JMb~YFBEvdsV)`mEQO^&#eRKx@b&x- z5lZm*!WfD8oCLzfHGz#u7sT0^VLMI1MqGxF^v+`4YYnVYgk*=kU?HsSz{v({E3lb9 z>+xILjBN)t6`=g~IBOelGQ(O990@BfXf(DRI5I$qN$0Gkz-FSc$3a+2fX$AedL4u{ z4V+5Ong(9LiGcIKW?_352sR;LtDPmPJXI{YtT=O8=76o9;*n%_m|xo!i>7$IrZ-{l z-x3`7M}qzHsPV@$v#>H-TpjDh2UE$9g6sysUREDy_R(a)>=eHw-WAyfIN z*qb!_hW>G)Tu8nSw9yn#3wFMiLcfc4pY0ek1}8(NqkBR@t4{~oC>ryc-h_ByH(Cg5 z>ao-}771+xE3um9lWAY1FeQFxowa1(!J(;Jg*wrg!=6FdRX+t_<%z&d&?|Bn){>zm zZQj(aA_HeBY&OC^jj*)N`8fa^ePOU72VpInJoI1?`ty#lvlNzs(&MZX+R%2xS~5Kh zX*|AU4QE#~SgPzOXe9>tRj>hjU@c1k5Y_mW*Jp3fI;)1&g3j|zDgC+}2Q_v%YfDax z!?umcN^n}KYQ|a$Lr+51Nf9dkkYFSjZZjkma$0KOj+;aQ&721~t7QUKx61J3(P4P1 zstI~7-wOACnWP4=8oGOwz%vNDqD8w&Q`qcNGGrbbf&0s9L0De{4{mRS?o0MU+nR_! zrvshUau0G^DeMhM_v{5BuLjb#Hh@r23lDAk8oF(C+P0rsBpv85EP>4CVMx#04MOfG z;P%vktHcXwTj~+IE(~px)3*MY77e}p#|c>TD?sMatC0Tu4iKKJ0(X8jxQY*gYtxsC z(zYC$g|@+I+kY;dg_dE>scBf&bP1Nc@Hz<3R)V`=AGkc;8CXqdi=B4l2k|g;2%#m& z*jfX^%b!A8#bI!j9-0Fi0bOXl(-c^AB9|nQaE`*)Hw+o&jS9@7&Gov#HbD~#d{twV zXd^Tr^mWLfFh$@Dr$e;PBEz4(-2q1FF0}c;~B5sA}+Q>TOoP+t>wf)V9Iy=5ruQa;z)y zI9C9*oUga6=hxw6QasLPnee@3^Rr*M{CdaL5=R41nLs(AHk_=Y+A9$2&H(B7!_pURs&8aNw7?`&Z&xY_Ye z)~D5Bog^td-^QbUtkTirdyK^mTHAOuptDflut!#^lnKqU md>ggs(5nOWAqO?umG&QVYK#ibz}*4>0000U6E9hRK9^#O7(mu>ETqrXGsduA8$)?`v2seloOCza43C{NQ$$gAOH**MCn0Q?+L7dl7qnbRdqZ8LSVp1ItDxhxD?t@5_yHg6A8yI zC*%Wgg22K|8E#!~cTNYR~@Y9KepMPrrB8cABapAFa=`H+UGhkXUZV1GnwR1*lPyZ;*K(i~2gp|@bzp8}og7e*#% zEnr|^CWdVV!-4*Y_7rFvlww2Ze+>j*!Z!pQ?2l->4q#nqRu9`ELo6RMS5=br47g_X zRw}P9a7RRYQ%2Vsd0Me{_(EggTnuN6j=-?uFS6j^u69elMypu?t>op*wBx<=Wx8?( ztpe^(fwM6jJX7M-l*k3kEpWOl_Vk3@(_w4oc}4YF4|Rt=2V^XU?#Yz`8(e?aZ@#li0n*=g^qOcVpd-Wbok=@b#Yw zqn8u9a)z>l(1kEaPYZ6hwubN6i<8QHgsu0oE) ziJ(p;Wxm>sf!K+cw>R-(^Y2_bahB+&KI9y^);#0qt}t-$C|Bo71lHi{_+lg#f%RFy z0um=e3$K3i6K{U_4K!EX?F&rExl^W|G8Z8;`5z-k}OGNZ0#WVb$WCpQu-_YsiqKP?BB# vzVHS-CTUF4Ozn5G+mq_~Qqto~ahA+K`|lyv3(-e}00000NkvXXu0mjfd`9t{ literal 0 HcmV?d00001 diff --git a/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d0ef06e7edb86cdfe0d15b4b0d98334a86163658 GIT binary patch literal 1716 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 literal 0 HcmV?d00001 diff --git a/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c8f9ed8f5cee1c98386d13b17e89f719e83555b2 GIT binary patch literal 1895 zcmV-t2blPYP)FQtfgmafE#=YDCq`qUBt#QpG%*H6QHY765~R=q zZ6iudfM}q!Pz#~9JgOi8QJ|DSu?1-*(kSi1K4#~5?#|rh?sS)(-JQqX*}ciXJ56_H zdw=^s_srbAdqxlvGyrgGet#6T7_|j;95sL%MtM;q86vOxKM$f#puR)Bjv9Zvz9-di zXOTSsZkM83)E9PYBXC<$6(|>lNLVBb&&6y{NByFCp%6+^ALR@NCTse_wqvNmSWI-m z!$%KlHFH2omF!>#%1l3LTZg(s7eof$7*xB)ZQ0h?ejh?Ta9fDv59+u#MokW+1t8Zb zgHv%K(u9G^Lv`lh#f3<6!JVTL3(dCpxHbnbA;kKqQyd1~^Xe0VIaYBSWm6nsr;dFj z4;G-RyL?cYgsN1{L4ZFFNa;8)Rv0fM0C(~Tkit94 zz#~A)59?QjD&pAPSEQ)p8gP|DS{ng)j=2ux)_EzzJ773GmQ_Cic%3JJhC0t2cx>|v zJcVusIB!%F90{+}8hG3QU4KNeKmK%T>mN57NnCZ^56=0?&3@!j>a>B43pi{!u z7JyDj7`6d)qVp^R=%j>UIY6f+3`+qzIc!Y_=+uN^3BYV|o+$vGo-j-Wm<10%A=(Yk^beI{t%ld@yhKjq0iNjqN4XMGgQtbKubPM$JWBz}YA65k%dm*awtC^+f;a-x4+ddbH^7iDWGg&N0n#MW{kA|=8iMUiFYvMoDY@sPC#t$55gn6ykUTPAr`a@!(;np824>2xJthS z*ZdmT`g5-`BuJs`0LVhz+D9NNa3<=6m;cQLaF?tCv8)zcRSh66*Z|vXhG@$I%U~2l z?`Q zykI#*+rQ=z6Jm=Bui-SfpDYLA=|vzGE(dYm=OC8XM&MDo7ux4UF1~0J1+i%aCUpRe zt3L_uNyQ*cE(38Uy03H%I*)*Bh=Lb^Xj3?I^Hnbeq72(EOK^Y93CNp*uAA{5Lc=ky zx=~RKa4{iTm{_>_vSCm?$Ej=i6@=m%@VvAITnigVg{&@!7CDgs908761meDK5azA} z4?=NOH|PdvabgJ&fW2{Mo$Q0CcD8Qc84%{JPYt5EiG{MdLIAeX%T=D7NIP4%Hw}p9 zg)==!2Lbp#j{u_}hMiao9=!VSyx0gHbeCS`;q&vzeq|fs`y&^X-lso(Ls@-706qmA z7u*T5PMo_w3{se1t2`zWeO^hOvTsohG_;>J0wVqVe+n)AbQCx)yh9;w+J6?NF5Lmo zecS@ieAKL8%bVd@+-KT{yI|S}O>pYckUFs;ry9Ow$CD@ztz5K-*D$^{i(_1llhSh^ zEkL$}tsQt5>QA^;QgjgIfBDmcOgi5YDyu?t6vSnbp=1+@6D& z5MJ}B8q;bRlVoxasyhcUF1+)o`&3r0colr}QJ3hcSdLu;9;td>kf@Tcn<@9sIx&=m z;AD;SCh95=&p;$r{Xz3iWCO^MX83AGJ(yH&eTXgv|0=34#-&WAmw{)U7OU9!Wz^!7 zZ%jZFi@JR;>Mhi7S>V7wQ176|FdW2m?&`qa(ScO^CFPR80HucLHOTy%5s*HR0^8)i h0WYBP*#0Ks^FNSabJA*5${_#%002ovPDHLkV1oKhTl@e3 literal 0 HcmV?d00001 diff --git a/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 diff --git a/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 diff --git a/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..75b2d164a5a98e212cca15ea7bf2ab5de5108680 GIT binary patch literal 3831 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 literal 0 HcmV?d00001 diff --git a/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..c4df70d39da7941ef3f6dcb7f06a192d8dcb308d GIT binary patch literal 1888 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 iW8Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000000..89c2725b70f --- /dev/null +++ b/examples/todo-list/flutter-todo-list/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/examples/todo-list/flutter-todo-list/ios/Runner/Base.lproj/LaunchScreen.storyboard b/examples/todo-list/flutter-todo-list/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000000..f2e259c7c93 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/todo-list/flutter-todo-list/ios/Runner/Base.lproj/Main.storyboard b/examples/todo-list/flutter-todo-list/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000000..f3c28516fb3 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/todo-list/flutter-todo-list/ios/Runner/Info.plist b/examples/todo-list/flutter-todo-list/ios/Runner/Info.plist new file mode 100644 index 00000000000..b2265502bea --- /dev/null +++ b/examples/todo-list/flutter-todo-list/ios/Runner/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Todosupabase + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + todosupabase + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/examples/todo-list/flutter-todo-list/ios/Runner/Runner-Bridging-Header.h b/examples/todo-list/flutter-todo-list/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000000..308a2a560b4 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/examples/todo-list/flutter-todo-list/lib/constant.dart b/examples/todo-list/flutter-todo-list/lib/constant.dart new file mode 100644 index 00000000000..19382e50ddd --- /dev/null +++ b/examples/todo-list/flutter-todo-list/lib/constant.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; + +boxSmall() => const SizedBox(height: 20); +boxLarge() => const SizedBox(height: 40); + +Widget buttonCustom(String text, Function() onPressed, bool filled) { + return GestureDetector( + onTap: onPressed, + child: Container( + height: 50, + width: 300, + decoration: BoxDecoration( + color: filled ? Colors.white : Colors.transparent, + border: Border.all( + color: Colors.white, + width: 2, + ), + borderRadius: BorderRadius.circular(20), + ), + child: Center( + child: Text( + text, + style: TextStyle( + color: filled ? Colors.black : Colors.white, + fontWeight: FontWeight.bold), + ), + ), + ), + ); +} + +Widget textFeildCustom(String hintText, TextEditingController ctlr) { + return SizedBox( + height: 70, + width: 300, + child: TextField( + controller: ctlr, + style: const TextStyle(color: Colors.white), + decoration: InputDecoration( + hintText: hintText, + hintStyle: const TextStyle( + color: Colors.white, + ), + enabledBorder: OutlineInputBorder( + borderSide: const BorderSide( + color: Colors.white, + width: 2, + ), + borderRadius: BorderRadius.circular(20), + ), + focusedBorder: OutlineInputBorder( + borderSide: const BorderSide( + color: Colors.white, + width: 2, + ), + borderRadius: BorderRadius.circular(20), + ), + ), + ), + ); +} + +Future displayTextInputDialog( + BuildContext context, + Function()? onTab, + TextEditingController taskCtrl, + TextEditingController dueCtrl, +) async { + return showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text('Add a todo'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: taskCtrl, + decoration: + const InputDecoration(hintText: "Add title of todo"), + ), + TextField( + controller: dueCtrl, + keyboardType: TextInputType.datetime, + decoration: + const InputDecoration(hintText: "Add Due Date in DD/MM"), + ), + boxSmall(), + MaterialButton( + onPressed: onTab, + child: const Text("Add Task", + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + )), + color: Color(0xff33b27b), + ) + ], + ), + ); + }); +} diff --git a/examples/todo-list/flutter-todo-list/lib/functions/auth.dart b/examples/todo-list/flutter-todo-list/lib/functions/auth.dart new file mode 100644 index 00000000000..7b05c862c99 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/lib/functions/auth.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; + +class AuthSupabase { + static final client = SupabaseClient( + "https://newknrbzotfpvatooqxz.supabase.co", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im5ld2tucmJ6b3RmcHZhdG9vcXh6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTQwMjM2ODYsImV4cCI6MTk2OTU5OTY4Nn0.KI6TzzzailhNRfRAeJMIDtMVZSBZbaliDK2K_J0VVn8"); + static Future createAccount( + {required String email, required String password}) async { + final res = await client.auth.signUp(email, password); + final user = res.data?.user; + final error = res.error; + print(user); + print(error); + if (error == null) { + return true; + } else { + return false; + } + } + + static Future loginUser( + {required String email, required String password}) async { + final res = await client.auth.signIn(email: email, password: password); + final user = res.data?.user; + final error = res.error; + return user?.id; + } + + static logOut(BuildContext context) async { + await client.auth.signOut(); + Navigator.pushNamed(context, '/'); + } +} diff --git a/examples/todo-list/flutter-todo-list/lib/functions/crud.dart b/examples/todo-list/flutter-todo-list/lib/functions/crud.dart new file mode 100644 index 00000000000..de2f81173ac --- /dev/null +++ b/examples/todo-list/flutter-todo-list/lib/functions/crud.dart @@ -0,0 +1,92 @@ +import 'dart:convert'; + +import 'package:todosupabase/functions/auth.dart'; + +class CrudSupabase { + static Future getUser({required String email}) async { + final res = await AuthSupabase.client + .from('Users') + .select() + .eq("email", email) + .execute(); + final data = res.data; + final error = res.error; + print(data); + return data; + } + + static Future> getTodo() async { + final res = await AuthSupabase.client + .from('Todo') + .select() + .eq("email", AuthSupabase.client.auth.currentUser!.email.toString()) + .execute(); + final data = res.data; + final error = res.error; + return data; + } + + static Future addTodo( + {required String task, required String due}) async { + final res = await AuthSupabase.client.from('Todo').insert({ + "email": AuthSupabase.client.auth.currentUser!.email.toString(), + "task": task, + "due": due, + "done": false + }).execute(); + final data = res.data; + final error = res.error; + print(data); + return data; + } + + static Future deleteTodo({required String id}) async { + final res = + await AuthSupabase.client.from('Todo').delete().eq("id", id).execute(); + final data = res.data; + final error = res.error; + print(data); + return data; + } + + static Future taskDone( + {required String id, required bool value}) async { + final res = await AuthSupabase.client + .from('Todo') + .update({"done": value}) + .eq("id", id) + .execute(); + final data = res.data; + final error = res.error; + print(data); + return data; + } + + static Future editTask( + {required String id, required String due, required String task}) async { + final res = await AuthSupabase.client + .from('Todo') + .update({"due": due, "task": task}) + .eq("id", id) + .execute(); + final data = res.data; + final error = res.error; + print(data); + return data; + } + + static Future addUser( + {required String name, required String email}) async { + final res = await AuthSupabase.client.from('Users').insert([ + { + "name": name, + "email": email, + } + ]).execute(); + print("adding number"); + final data = res.data; + final error = res.error; + print(data); + print(error); + } +} diff --git a/examples/todo-list/flutter-todo-list/lib/main.dart b/examples/todo-list/flutter-todo-list/lib/main.dart new file mode 100644 index 00000000000..a3849e6653d --- /dev/null +++ b/examples/todo-list/flutter-todo-list/lib/main.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; +import 'package:todosupabase/constant.dart'; +import 'package:todosupabase/functions/auth.dart'; +import 'package:todosupabase/screens/signup.dart'; +import 'package:todosupabase/screens/signin.dart'; +import 'package:todosupabase/screens/todo.dart'; + +void main() async { + await Supabase.initialize( + url: 'https://newknrbzotfpvatooqxz.supabase.co', + anonKey: + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im5ld2tucmJ6b3RmcHZhdG9vcXh6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTQwMjM2ODYsImV4cCI6MTk2OTU5OTY4Nn0.KI6TzzzailhNRfRAeJMIDtMVZSBZbaliDK2K_J0VVn8'); + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + theme: ThemeData.dark(), + darkTheme: ThemeData( + scaffoldBackgroundColor: Colors.black, + textTheme: const TextTheme( + bodyText1: TextStyle(color: Colors.white), + bodyText2: TextStyle(color: Colors.white), + ), + ), + initialRoute: "/", + routes: { + "/": (context) => const MyHomePage(), + "/signup": (context) => const SignUpPage(), + "/signin": (context) => const SignInPage(), + "/todo": (context) => const TodoPage(), + }, + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({Key? key}) : super(key: key); + + @override + State createState() => MyHomePageState(); +} + +class MyHomePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + body: SizedBox( + height: double.infinity, + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SvgPicture.asset("assets/supabase-dark.svg", width: 200), + boxLarge(), + buttonCustom("Sign In", () { + Navigator.pushNamed(context, "/signin"); + }, false), + boxSmall(), + buttonCustom("Sign Up", () { + Navigator.pushNamed(context, "/signup"); + }, false) + ], + ), + ), + ); + } +} diff --git a/examples/todo-list/flutter-todo-list/lib/model.dart b/examples/todo-list/flutter-todo-list/lib/model.dart new file mode 100644 index 00000000000..5c9d8c1d1da --- /dev/null +++ b/examples/todo-list/flutter-todo-list/lib/model.dart @@ -0,0 +1,12 @@ +import 'package:todosupabase/functions/auth.dart'; + +class TodoModel { + String? task; + bool done = false; + String email = AuthSupabase.client.auth.currentUser!.email.toString(); + String? due; + TodoModel({ + required String task, + required String due, + }); +} diff --git a/examples/todo-list/flutter-todo-list/lib/screens/signin.dart b/examples/todo-list/flutter-todo-list/lib/screens/signin.dart new file mode 100644 index 00000000000..ed46c061ccc --- /dev/null +++ b/examples/todo-list/flutter-todo-list/lib/screens/signin.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:todosupabase/constant.dart'; +import 'package:todosupabase/functions/auth.dart'; +import 'package:todosupabase/functions/crud.dart'; + +class SignInPage extends StatefulWidget { + const SignInPage({Key? key}) : super(key: key); + + @override + State createState() => _SignInPageState(); +} + +class _SignInPageState extends State { + final TextEditingController _emailController = TextEditingController(); + final TextEditingController _passwordController = TextEditingController(); + bool loading = false; + @override + Widget build(BuildContext context) { + return Scaffold( + body: SizedBox( + height: double.infinity, + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SvgPicture.asset("assets/supabase-dark.svg", width: 200), + boxLarge(), + textFeildCustom( + "Email", + _emailController, + ), + boxSmall(), + textFeildCustom( + "Password", + _passwordController, + ), + boxSmall(), + loading + ? Container( + height: 50, + width: 50, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(50), + ), + child: const Center( + child: CircularProgressIndicator(), + ), + ) + : buttonCustom("Sign In", () { + setState(() { + loading = true; + }); + AuthSupabase.loginUser( + email: _emailController.text, + password: _passwordController.text) + .then((value) { + setState(() { + loading = false; + }); + if (value != null) { + Navigator.pushNamed(context, '/todo'); + } + }); + }, true) + ], + ), + ), + ); + } +} diff --git a/examples/todo-list/flutter-todo-list/lib/screens/signup.dart b/examples/todo-list/flutter-todo-list/lib/screens/signup.dart new file mode 100644 index 00000000000..1c72734b17f --- /dev/null +++ b/examples/todo-list/flutter-todo-list/lib/screens/signup.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:todosupabase/constant.dart'; +import 'package:todosupabase/functions/auth.dart'; +import 'package:todosupabase/functions/crud.dart'; + +class SignUpPage extends StatefulWidget { + const SignUpPage({Key? key}) : super(key: key); + + @override + State createState() => _SignUpPageState(); +} + +class _SignUpPageState extends State { + final TextEditingController _emailController = TextEditingController(); + final TextEditingController _passwordController = TextEditingController(); + final TextEditingController _nameController = TextEditingController(); + @override + Widget build(BuildContext context) { + return Scaffold( + body: SizedBox( + height: double.infinity, + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SvgPicture.asset("assets/supabase-dark.svg", width: 200), + boxLarge(), + textFeildCustom( + "Full Name", + _nameController, + ), + boxSmall(), + textFeildCustom( + "Email", + _emailController, + ), + boxSmall(), + textFeildCustom( + "Password", + _passwordController, + ), + boxSmall(), + buttonCustom("Sign Up", () { + AuthSupabase.createAccount( + email: _emailController.text, + password: _passwordController.text) + .then((value) { + if (value == true) { + CrudSupabase.addUser( + name: _nameController.text, email: _emailController.text); + Navigator.pushNamed(context, "/signin"); + } + }); + }, true) + ], + ), + ), + ); + ; + } +} diff --git a/examples/todo-list/flutter-todo-list/lib/screens/todo.dart b/examples/todo-list/flutter-todo-list/lib/screens/todo.dart new file mode 100644 index 00000000000..1da6813cde1 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/lib/screens/todo.dart @@ -0,0 +1,211 @@ +import 'package:flutter/material.dart'; +import 'package:todosupabase/constant.dart'; +import 'package:todosupabase/functions/auth.dart'; +import 'package:todosupabase/functions/crud.dart'; + +class TodoPage extends StatefulWidget { + const TodoPage({Key? key}) : super(key: key); + + @override + State createState() => _TodoPageState(); +} + +class _TodoPageState extends State { + String userName = ""; + bool loading = true; + List todos = []; + @override + void initState() { + // TODO: implement initState + super.initState(); + CrudSupabase.getUser( + email: AuthSupabase.client.auth.currentUser!.email.toString()) + .then((value) { + setState(() { + userName = value[0]['name']; + }); + }); + CrudSupabase.getTodo().then((value) { + value.forEach((element) { + todos.add(element); + }); + }); + setState(() { + loading = false; + }); + } + + @override + Widget build(BuildContext context) { + final TextEditingController _taskEditingController = + TextEditingController(); + final TextEditingController _dueEditingController = TextEditingController(); + return Scaffold( + floatingActionButton: FloatingActionButton( + backgroundColor: const Color(0xff33b27b), + onPressed: () { + displayTextInputDialog(context, () { + CrudSupabase.addTodo( + task: _taskEditingController.text, + due: _dueEditingController.text, + ).then((value) { + Navigator.pop(context); + setState(() { + loading = true; + }); + CrudSupabase.getTodo().then((value) { + todos.clear(); + setState(() {}); + value.forEach((element) { + todos.add(element); + }); + }); + setState(() { + loading = false; + }); + }); + }, _taskEditingController, _dueEditingController); + }, + child: const Icon(Icons.add), + ), + appBar: AppBar( + backgroundColor: Colors.black, + automaticallyImplyLeading: false, + title: Text('Hey ${userName.split(" ")[0]}, add todo'), + actions: [ + IconButton( + icon: const Icon(Icons.exit_to_app), + onPressed: () { + AuthSupabase.logOut(context); + }, + ), + ], + ), + body: loading + ? const Center(child: CircularProgressIndicator()) + : SizedBox( + height: double.infinity, + width: double.infinity, + child: todos.isEmpty + ? const Center( + child: Text("No Task, add new task"), + ) + : Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: todos + .map( + (e) => ListTile( + //Long Press to edit + onLongPress: () { + displayTextInputDialog(context, () { + CrudSupabase.editTask( + id: e["id"].toString(), + due: _dueEditingController.text, + task: _taskEditingController.text) + .then((value) { + Navigator.pop(context); + setState(() { + loading = true; + }); + CrudSupabase.getTodo().then((value) { + todos.clear(); + setState(() {}); + value.forEach((element) { + todos.add(element); + }); + }); + setState(() { + loading = false; + }); + }); + }, _taskEditingController, + _dueEditingController); + }, + title: Text( + e["task"], + style: TextStyle(color: Colors.white), + ), + subtitle: Text( + e["due"], + style: TextStyle(color: Colors.white), + ), + leading: GestureDetector( + onTap: () { + setState(() { + loading = true; + }); + CrudSupabase.deleteTodo( + id: e["id"].toString()) + .then((value) { + print("delteed"); + CrudSupabase.getTodo().then((value) { + todos.clear(); + setState(() {}); + value.forEach((element) { + todos.add(element); + }); + }); + setState(() { + loading = false; + }); + }); + }, + child: const Icon( + Icons.delete, + color: Colors.white, + ), + ), + trailing: GestureDetector( + onTap: () { + setState(() { + loading = true; + }); + CrudSupabase.taskDone( + id: e["id"].toString(), + value: !e["done"]) + .then((value) { + CrudSupabase.getTodo().then((value) { + todos.clear(); + setState(() {}); + value.forEach((element) { + todos.add(element); + }); + setState(() { + loading = false; + }); + }); + }); + }, + child: Container( + height: 25, + width: 25, + decoration: BoxDecoration( + border: Border.all( + color: const Color(0xff33b27b), + width: 2, + ), + color: e["done"] + ? const Color(0xff33b27b) + : Colors.transparent, + borderRadius: BorderRadius.circular(5), + ), + child: e["done"] + ? const Center( + child: Icon( + Icons.done, + size: 15, + color: Colors.white, + ), + ) + : const SizedBox(), + ), + ), + ), + ) + .toList(), + ), + ), + ); + } +} diff --git a/examples/todo-list/flutter-todo-list/pubspec.lock b/examples/todo-list/flutter-todo-list/pubspec.lock new file mode 100644 index 00000000000..af291b99057 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/pubspec.lock @@ -0,0 +1,509 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.8.2" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.15.0" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.2" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + functions_client: + dependency: transitive + description: + name: functions_client + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1-dev.5" + gotrue: + dependency: transitive + description: + name: gotrue + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.2" + hive: + dependency: transitive + description: + name: hive + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" + hive_flutter: + dependency: transitive + description: + name: hive_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + http: + dependency: transitive + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.4" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.1" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.3" + jwt_decode: + dependency: transitive + description: + name: jwt_decode + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.1" + lints: + dependency: transitive + description: + name: lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.11" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.0" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + path_drawing: + dependency: transitive + description: + name: path_drawing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + path_parsing: + dependency: transitive + description: + name: path_parsing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + path_provider: + dependency: transitive + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.10" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.14" + path_provider_ios: + dependency: transitive + description: + name: path_provider_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.9" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.7" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.7" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "4.4.0" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + postgrest: + dependency: transitive + description: + name: postgrest + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.10+1" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.4" + realtime_client: + dependency: transitive + description: + name: realtime_client + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.15" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + storage_client: + dependency: transitive + description: + name: storage_client + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.6+2" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + supabase: + dependency: transitive + description: + name: supabase + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.4" + supabase_flutter: + dependency: "direct main" + description: + name: supabase_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.1+1" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.8" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + uni_links: + dependency: transitive + description: + name: uni_links + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.1" + uni_links_platform_interface: + dependency: transitive + description: + name: uni_links_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + uni_links_web: + dependency: transitive + description: + name: uni_links_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0" + universal_io: + dependency: transitive + description: + name: universal_io + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + url_launcher: + dependency: transitive + description: + name: url_launcher + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.17" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.17" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "2.5.2" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0+1" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "5.3.1" +sdks: + dart: ">=2.16.1 <3.0.0" + flutter: ">=2.10.0" diff --git a/examples/todo-list/flutter-todo-list/pubspec.yaml b/examples/todo-list/flutter-todo-list/pubspec.yaml new file mode 100644 index 00000000000..bb6f1b9d1c4 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/pubspec.yaml @@ -0,0 +1,88 @@ +name: todosupabase +description: A new Flutter project. + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: "none" # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.0+1 + +environment: + sdk: ">=2.16.1 <3.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + supabase_flutter: ^0.3.1+1 + flutter_svg: ^1.0.3 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^1.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + assets: + - assets/ + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/examples/todo-list/flutter-todo-list/test/widget_test.dart b/examples/todo-list/flutter-todo-list/test/widget_test.dart new file mode 100644 index 00000000000..d2e1174ab0d --- /dev/null +++ b/examples/todo-list/flutter-todo-list/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:todosupabase/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/examples/todo-list/flutter-todo-list/web/favicon.png b/examples/todo-list/flutter-todo-list/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8aaa46ac1ae21512746f852a42ba87e4165dfdd1 GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*SYAX-%d(5gVjrHJWqXQshj@!Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM literal 0 HcmV?d00001 diff --git a/examples/todo-list/flutter-todo-list/web/icons/Icon-192.png b/examples/todo-list/flutter-todo-list/web/icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..b749bfef07473333cf1dd31e9eed89862a5d52aa GIT binary patch literal 5292 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 literal 0 HcmV?d00001 diff --git a/examples/todo-list/flutter-todo-list/web/icons/Icon-512.png b/examples/todo-list/flutter-todo-list/web/icons/Icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..88cfd48dff1169879ba46840804b412fe02fefd6 GIT binary patch literal 8252 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 literal 0 HcmV?d00001 diff --git a/examples/todo-list/flutter-todo-list/web/icons/Icon-maskable-192.png b/examples/todo-list/flutter-todo-list/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9b4d76e525556d5d89141648c724331630325d GIT binary patch literal 5594 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 X>c!)3PgKfL&lX^$Z>Cpu&6)6jvi^Z! literal 0 HcmV?d00001 diff --git a/examples/todo-list/flutter-todo-list/web/icons/Icon-maskable-512.png b/examples/todo-list/flutter-todo-list/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..d69c56691fbdb0b7efa65097c7cc1edac12a6d3e GIT binary patch literal 20998 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 MPgg&ebxsLQ025IeI{*Lx literal 0 HcmV?d00001 diff --git a/examples/todo-list/flutter-todo-list/web/index.html b/examples/todo-list/flutter-todo-list/web/index.html new file mode 100644 index 00000000000..2f674a9a5df --- /dev/null +++ b/examples/todo-list/flutter-todo-list/web/index.html @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + todosupabase + + + + + + + diff --git a/examples/todo-list/flutter-todo-list/web/manifest.json b/examples/todo-list/flutter-todo-list/web/manifest.json new file mode 100644 index 00000000000..101869135b0 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "todosupabase", + "short_name": "todosupabase", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/examples/todo-list/flutter-todo-list/windows/.gitignore b/examples/todo-list/flutter-todo-list/windows/.gitignore new file mode 100644 index 00000000000..d492d0d98c8 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/examples/todo-list/flutter-todo-list/windows/CMakeLists.txt b/examples/todo-list/flutter-todo-list/windows/CMakeLists.txt new file mode 100644 index 00000000000..b327d83ddfa --- /dev/null +++ b/examples/todo-list/flutter-todo-list/windows/CMakeLists.txt @@ -0,0 +1,95 @@ +cmake_minimum_required(VERSION 3.14) +project(todosupabase LANGUAGES CXX) + +set(BINARY_NAME "todosupabase") + +cmake_policy(SET CMP0063 NEW) + +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Configure build options. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() + +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") + +# Flutter library and tool build rules. +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build +add_subdirectory("runner") + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/examples/todo-list/flutter-todo-list/windows/flutter/CMakeLists.txt b/examples/todo-list/flutter-todo-list/windows/flutter/CMakeLists.txt new file mode 100644 index 00000000000..b2e4bd8d658 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/windows/flutter/CMakeLists.txt @@ -0,0 +1,103 @@ +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + windows-x64 $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/examples/todo-list/flutter-todo-list/windows/flutter/generated_plugin_registrant.cc b/examples/todo-list/flutter-todo-list/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000000..4f7884874da --- /dev/null +++ b/examples/todo-list/flutter-todo-list/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/examples/todo-list/flutter-todo-list/windows/flutter/generated_plugin_registrant.h b/examples/todo-list/flutter-todo-list/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000000..dc139d85a93 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/examples/todo-list/flutter-todo-list/windows/flutter/generated_plugins.cmake b/examples/todo-list/flutter-todo-list/windows/flutter/generated_plugins.cmake new file mode 100644 index 00000000000..411af46dd72 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/windows/flutter/generated_plugins.cmake @@ -0,0 +1,16 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_windows +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) diff --git a/examples/todo-list/flutter-todo-list/windows/runner/CMakeLists.txt b/examples/todo-list/flutter-todo-list/windows/runner/CMakeLists.txt new file mode 100644 index 00000000000..de2d8916b72 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/windows/runner/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) +apply_standard_settings(${BINARY_NAME}) +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/examples/todo-list/flutter-todo-list/windows/runner/Runner.rc b/examples/todo-list/flutter-todo-list/windows/runner/Runner.rc new file mode 100644 index 00000000000..d5d06e6d49f --- /dev/null +++ b/examples/todo-list/flutter-todo-list/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#ifdef FLUTTER_BUILD_NUMBER +#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#else +#define VERSION_AS_NUMBER 1,0,0 +#endif + +#ifdef FLUTTER_BUILD_NAME +#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "todosupabase" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "todosupabase" "\0" + VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "todosupabase.exe" "\0" + VALUE "ProductName", "todosupabase" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/examples/todo-list/flutter-todo-list/windows/runner/flutter_window.cpp b/examples/todo-list/flutter-todo-list/windows/runner/flutter_window.cpp new file mode 100644 index 00000000000..b43b9095ea3 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/windows/runner/flutter_window.cpp @@ -0,0 +1,61 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/examples/todo-list/flutter-todo-list/windows/runner/flutter_window.h b/examples/todo-list/flutter-todo-list/windows/runner/flutter_window.h new file mode 100644 index 00000000000..6da0652f05f --- /dev/null +++ b/examples/todo-list/flutter-todo-list/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/examples/todo-list/flutter-todo-list/windows/runner/main.cpp b/examples/todo-list/flutter-todo-list/windows/runner/main.cpp new file mode 100644 index 00000000000..530c79c58e5 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.CreateAndShow(L"todosupabase", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/examples/todo-list/flutter-todo-list/windows/runner/resource.h b/examples/todo-list/flutter-todo-list/windows/runner/resource.h new file mode 100644 index 00000000000..66a65d1e4a7 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/examples/todo-list/flutter-todo-list/windows/runner/resources/app_icon.ico b/examples/todo-list/flutter-todo-list/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c04e20caf6370ebb9253ad831cc31de4a9c965f6 GIT binary patch literal 33772 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 literal 0 HcmV?d00001 diff --git a/examples/todo-list/flutter-todo-list/windows/runner/runner.exe.manifest b/examples/todo-list/flutter-todo-list/windows/runner/runner.exe.manifest new file mode 100644 index 00000000000..c977c4a4258 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/examples/todo-list/flutter-todo-list/windows/runner/utils.cpp b/examples/todo-list/flutter-todo-list/windows/runner/utils.cpp new file mode 100644 index 00000000000..d19bdbbcc32 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/windows/runner/utils.cpp @@ -0,0 +1,64 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr); + if (target_length == 0) { + return std::string(); + } + std::string utf8_string; + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, utf8_string.data(), + target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/examples/todo-list/flutter-todo-list/windows/runner/utils.h b/examples/todo-list/flutter-todo-list/windows/runner/utils.h new file mode 100644 index 00000000000..3879d547557 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/examples/todo-list/flutter-todo-list/windows/runner/win32_window.cpp b/examples/todo-list/flutter-todo-list/windows/runner/win32_window.cpp new file mode 100644 index 00000000000..c10f08dc7da --- /dev/null +++ b/examples/todo-list/flutter-todo-list/windows/runner/win32_window.cpp @@ -0,0 +1,245 @@ +#include "win32_window.h" + +#include + +#include "resource.h" + +namespace { + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + FreeLibrary(user32_module); + } +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + return OnCreate(); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} diff --git a/examples/todo-list/flutter-todo-list/windows/runner/win32_window.h b/examples/todo-list/flutter-todo-list/windows/runner/win32_window.h new file mode 100644 index 00000000000..17ba431125b --- /dev/null +++ b/examples/todo-list/flutter-todo-list/windows/runner/win32_window.h @@ -0,0 +1,98 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates and shows a win32 window with |title| and position and size using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size to will treat the width height passed in to this function + // as logical pixels and scale to appropriate for the default monitor. Returns + // true if the window was created successfully. + bool CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responsponds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ From 6770020c2efc183cd0d1d5dd9b4a01808f71bb98 Mon Sep 17 00:00:00 2001 From: Aniket Singh <63359563+Anikets08@users.noreply.github.com> Date: Fri, 3 Jun 2022 15:03:56 +0530 Subject: [PATCH 02/57] Update examples/todo-list/flutter-todo-list/lib/main.dart Co-authored-by: Tyler <18113850+dshukertjr@users.noreply.github.com> --- examples/todo-list/flutter-todo-list/lib/main.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/todo-list/flutter-todo-list/lib/main.dart b/examples/todo-list/flutter-todo-list/lib/main.dart index a3849e6653d..ede922e9a5e 100644 --- a/examples/todo-list/flutter-todo-list/lib/main.dart +++ b/examples/todo-list/flutter-todo-list/lib/main.dart @@ -9,9 +9,9 @@ import 'package:todosupabase/screens/todo.dart'; void main() async { await Supabase.initialize( - url: 'https://newknrbzotfpvatooqxz.supabase.co', - anonKey: - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im5ld2tucmJ6b3RmcHZhdG9vcXh6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTQwMjM2ODYsImV4cCI6MTk2OTU5OTY4Nn0.KI6TzzzailhNRfRAeJMIDtMVZSBZbaliDK2K_J0VVn8'); + url: 'YOUR_SUPABASE_URL', + anonKey: 'YOUR_SUPABASE_KEY', + ); runApp(const MyApp()); } From b3649fd17274d6dbdcee1dde6445764f34e0c004 Mon Sep 17 00:00:00 2001 From: Aniket Singh <31aniket.singh@gmail.com> Date: Fri, 3 Jun 2022 16:02:23 +0530 Subject: [PATCH 03/57] added listView instead of column and await instead of then --- .../todo-list/flutter-todo-list/lib/main.dart | 5 +- .../flutter-todo-list/lib/screens/todo.dart | 293 +++++++++--------- .../todo-list/flutter-todo-list/pubspec.lock | 7 - .../todo-list/flutter-todo-list/pubspec.yaml | 1 - 4 files changed, 146 insertions(+), 160 deletions(-) diff --git a/examples/todo-list/flutter-todo-list/lib/main.dart b/examples/todo-list/flutter-todo-list/lib/main.dart index ede922e9a5e..7ffdff4f02a 100644 --- a/examples/todo-list/flutter-todo-list/lib/main.dart +++ b/examples/todo-list/flutter-todo-list/lib/main.dart @@ -2,15 +2,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:todosupabase/constant.dart'; -import 'package:todosupabase/functions/auth.dart'; import 'package:todosupabase/screens/signup.dart'; import 'package:todosupabase/screens/signin.dart'; import 'package:todosupabase/screens/todo.dart'; void main() async { await Supabase.initialize( - url: 'YOUR_SUPABASE_URL', - anonKey: 'YOUR_SUPABASE_KEY', + url: 'YOUR_SUPABASE_URL', + anonKey: 'YOUR_SUPABASE_KEY', ); runApp(const MyApp()); } diff --git a/examples/todo-list/flutter-todo-list/lib/screens/todo.dart b/examples/todo-list/flutter-todo-list/lib/screens/todo.dart index 1da6813cde1..6aefbdcecfc 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/todo.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/todo.dart @@ -14,55 +14,62 @@ class _TodoPageState extends State { String userName = ""; bool loading = true; List todos = []; - @override - void initState() { - // TODO: implement initState - super.initState(); - CrudSupabase.getUser( - email: AuthSupabase.client.auth.currentUser!.email.toString()) - .then((value) { - setState(() { - userName = value[0]['name']; - }); + final TextEditingController _taskEditingController = TextEditingController(); + final TextEditingController _dueEditingController = TextEditingController(); + + void initFunc() async { + var nameVal = await CrudSupabase.getUser( + email: AuthSupabase.client.auth.currentUser!.email.toString()); + setState(() { + userName = nameVal[0]['name']; }); - CrudSupabase.getTodo().then((value) { - value.forEach((element) { - todos.add(element); - }); + List value = await CrudSupabase.getTodo(); + value.forEach((element) { + todos.add(element); }); setState(() { loading = false; }); } + @override + void initState() { + // TODO: implement initState + super.initState(); + initFunc(); + } + + @override + void dispose() { + super.dispose(); + _taskEditingController.dispose(); + _dueEditingController.dispose(); + } + @override Widget build(BuildContext context) { - final TextEditingController _taskEditingController = - TextEditingController(); - final TextEditingController _dueEditingController = TextEditingController(); return Scaffold( floatingActionButton: FloatingActionButton( backgroundColor: const Color(0xff33b27b), onPressed: () { - displayTextInputDialog(context, () { - CrudSupabase.addTodo( + displayTextInputDialog(context, () async { + await CrudSupabase.addTodo( task: _taskEditingController.text, due: _dueEditingController.text, - ).then((value) { - Navigator.pop(context); - setState(() { - loading = true; - }); - CrudSupabase.getTodo().then((value) { - todos.clear(); - setState(() {}); - value.forEach((element) { - todos.add(element); - }); - }); - setState(() { - loading = false; - }); + ); + Navigator.pop(context); + setState(() { + loading = true; + }); + + List value = await CrudSupabase.getTodo(); + todos.clear(); + setState(() {}); + value.forEach((element) { + todos.add(element); + }); + setState(() { + loading = false; }); }, _taskEditingController, _dueEditingController); }, @@ -90,122 +97,110 @@ class _TodoPageState extends State { ? const Center( child: Text("No Task, add new task"), ) - : Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: todos - .map( - (e) => ListTile( - //Long Press to edit - onLongPress: () { - displayTextInputDialog(context, () { - CrudSupabase.editTask( - id: e["id"].toString(), - due: _dueEditingController.text, - task: _taskEditingController.text) - .then((value) { - Navigator.pop(context); - setState(() { - loading = true; - }); - CrudSupabase.getTodo().then((value) { - todos.clear(); - setState(() {}); - value.forEach((element) { - todos.add(element); - }); - }); - setState(() { - loading = false; - }); - }); - }, _taskEditingController, - _dueEditingController); - }, - title: Text( - e["task"], - style: TextStyle(color: Colors.white), - ), - subtitle: Text( - e["due"], - style: TextStyle(color: Colors.white), - ), - leading: GestureDetector( - onTap: () { - setState(() { - loading = true; - }); - CrudSupabase.deleteTodo( - id: e["id"].toString()) - .then((value) { - print("delteed"); - CrudSupabase.getTodo().then((value) { - todos.clear(); - setState(() {}); - value.forEach((element) { - todos.add(element); - }); - }); - setState(() { - loading = false; - }); - }); - }, - child: const Icon( - Icons.delete, - color: Colors.white, - ), - ), - trailing: GestureDetector( - onTap: () { - setState(() { - loading = true; - }); - CrudSupabase.taskDone( - id: e["id"].toString(), - value: !e["done"]) - .then((value) { - CrudSupabase.getTodo().then((value) { - todos.clear(); - setState(() {}); - value.forEach((element) { - todos.add(element); - }); - setState(() { - loading = false; - }); - }); - }); - }, - child: Container( - height: 25, - width: 25, - decoration: BoxDecoration( - border: Border.all( - color: const Color(0xff33b27b), - width: 2, - ), - color: e["done"] - ? const Color(0xff33b27b) - : Colors.transparent, - borderRadius: BorderRadius.circular(5), - ), - child: e["done"] - ? const Center( - child: Icon( - Icons.done, - size: 15, - color: Colors.white, - ), - ) - : const SizedBox(), - ), - ), + : ListView.builder( + itemCount: todos.length, + itemBuilder: (context, index) { + return ListTile( + //Long Press to edit + onLongPress: () { + displayTextInputDialog(context, () async { + await CrudSupabase.editTask( + id: todos[index]["id"].toString(), + due: _dueEditingController.text, + task: _taskEditingController.text); + Navigator.pop(context); + setState(() { + loading = true; + }); + List value = + await CrudSupabase.getTodo(); + todos.clear(); + setState(() {}); + value.forEach((element) { + todos.add(element); + }); + setState(() { + loading = false; + }); + }, _taskEditingController, _dueEditingController); + }, + title: Text( + todos[index]["task"], + style: TextStyle(color: Colors.white), + ), + subtitle: Text( + todos[index]["due"], + style: TextStyle(color: Colors.white), + ), + leading: GestureDetector( + onTap: () async { + setState(() { + loading = true; + }); + await CrudSupabase.deleteTodo( + id: todos[index]["id"].toString()); + List value = + await CrudSupabase.getTodo(); + todos.clear(); + setState(() {}); + value.forEach((element) { + todos.add(element); + }); + setState(() { + loading = false; + }); + }, + child: const Icon( + Icons.delete, + color: Colors.white, ), - ) - .toList(), - ), - ), + ), + trailing: GestureDetector( + onTap: () async { + setState(() { + loading = true; + }); + await CrudSupabase.taskDone( + id: todos[index]["id"].toString(), + value: !todos[index]["done"]); + List value = + await CrudSupabase.getTodo(); + todos.clear(); + setState(() {}); + value.forEach((element) { + todos.add(element); + }); + setState(() { + loading = false; + }); + }, + child: Container( + height: 25, + width: 25, + decoration: BoxDecoration( + border: Border.all( + color: const Color(0xff33b27b), + width: 2, + ), + color: todos[index]["done"] + ? const Color(0xff33b27b) + : Colors.transparent, + borderRadius: BorderRadius.circular(5), + ), + child: todos[index]["done"] + ? const Center( + child: Icon( + Icons.done, + size: 15, + color: Colors.white, + ), + ) + : const SizedBox(), + ), + ), + ); + }, + )), ); } } diff --git a/examples/todo-list/flutter-todo-list/pubspec.lock b/examples/todo-list/flutter-todo-list/pubspec.lock index af291b99057..a899da6fff7 100644 --- a/examples/todo-list/flutter-todo-list/pubspec.lock +++ b/examples/todo-list/flutter-todo-list/pubspec.lock @@ -50,13 +50,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.2" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.4" fake_async: dependency: transitive description: diff --git a/examples/todo-list/flutter-todo-list/pubspec.yaml b/examples/todo-list/flutter-todo-list/pubspec.yaml index bb6f1b9d1c4..4fbe0d09274 100644 --- a/examples/todo-list/flutter-todo-list/pubspec.yaml +++ b/examples/todo-list/flutter-todo-list/pubspec.yaml @@ -32,7 +32,6 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 supabase_flutter: ^0.3.1+1 flutter_svg: ^1.0.3 From e04e48e0e7d6d1b41bf7597b207d66ec8518976c Mon Sep 17 00:00:00 2001 From: Aniket Singh <31aniket.singh@gmail.com> Date: Sat, 4 Jun 2022 11:39:53 +0530 Subject: [PATCH 04/57] code refactored and snackbar added for errors --- .../flutter-todo-list/lib/constant.dart | 17 +++++++++++- .../flutter-todo-list/lib/functions/auth.dart | 11 ++------ .../flutter-todo-list/lib/functions/crud.dart | 10 +++---- .../todo-list/flutter-todo-list/lib/main.dart | 22 ++++++++++----- .../flutter-todo-list/lib/screens/signin.dart | 27 +++++++++++-------- .../flutter-todo-list/lib/screens/signup.dart | 24 +++++++++-------- .../flutter-todo-list/lib/screens/todo.dart | 7 ++--- 7 files changed, 70 insertions(+), 48 deletions(-) diff --git a/examples/todo-list/flutter-todo-list/lib/constant.dart b/examples/todo-list/flutter-todo-list/lib/constant.dart index 19382e50ddd..9570c7f0dee 100644 --- a/examples/todo-list/flutter-todo-list/lib/constant.dart +++ b/examples/todo-list/flutter-todo-list/lib/constant.dart @@ -1,9 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; boxSmall() => const SizedBox(height: 20); boxLarge() => const SizedBox(height: 40); -Widget buttonCustom(String text, Function() onPressed, bool filled) { +Widget buttonCustom( + {required String text, + required Function() onPressed, + required bool filled}) { return GestureDetector( onTap: onPressed, child: Container( @@ -100,3 +104,14 @@ Future displayTextInputDialog( ); }); } + +ScaffoldFeatureController snackbarAlert( + {required BuildContext context, required String message}) { + return ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + duration: const Duration(seconds: 2), + backgroundColor: Colors.red, + ), + ); +} diff --git a/examples/todo-list/flutter-todo-list/lib/functions/auth.dart b/examples/todo-list/flutter-todo-list/lib/functions/auth.dart index 7b05c862c99..62e5d88b425 100644 --- a/examples/todo-list/flutter-todo-list/lib/functions/auth.dart +++ b/examples/todo-list/flutter-todo-list/lib/functions/auth.dart @@ -2,16 +2,11 @@ import 'package:flutter/material.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; class AuthSupabase { - static final client = SupabaseClient( - "https://newknrbzotfpvatooqxz.supabase.co", - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im5ld2tucmJ6b3RmcHZhdG9vcXh6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTQwMjM2ODYsImV4cCI6MTk2OTU5OTY4Nn0.KI6TzzzailhNRfRAeJMIDtMVZSBZbaliDK2K_J0VVn8"); + static final client = Supabase.instance.client; static Future createAccount( {required String email, required String password}) async { final res = await client.auth.signUp(email, password); - final user = res.data?.user; final error = res.error; - print(user); - print(error); if (error == null) { return true; } else { @@ -23,12 +18,10 @@ class AuthSupabase { {required String email, required String password}) async { final res = await client.auth.signIn(email: email, password: password); final user = res.data?.user; - final error = res.error; return user?.id; } - static logOut(BuildContext context) async { + static Future logOut(BuildContext context) async { await client.auth.signOut(); - Navigator.pushNamed(context, '/'); } } diff --git a/examples/todo-list/flutter-todo-list/lib/functions/crud.dart b/examples/todo-list/flutter-todo-list/lib/functions/crud.dart index de2f81173ac..fed4b0aa6ca 100644 --- a/examples/todo-list/flutter-todo-list/lib/functions/crud.dart +++ b/examples/todo-list/flutter-todo-list/lib/functions/crud.dart @@ -77,12 +77,10 @@ class CrudSupabase { static Future addUser( {required String name, required String email}) async { - final res = await AuthSupabase.client.from('Users').insert([ - { - "name": name, - "email": email, - } - ]).execute(); + final res = await AuthSupabase.client.from('Users').insert({ + "name": name, + "email": email, + }).execute(); print("adding number"); final data = res.data; final error = res.error; diff --git a/examples/todo-list/flutter-todo-list/lib/main.dart b/examples/todo-list/flutter-todo-list/lib/main.dart index 7ffdff4f02a..19d00d10e7c 100644 --- a/examples/todo-list/flutter-todo-list/lib/main.dart +++ b/examples/todo-list/flutter-todo-list/lib/main.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:todosupabase/constant.dart'; +import 'package:todosupabase/functions/auth.dart'; import 'package:todosupabase/screens/signup.dart'; import 'package:todosupabase/screens/signin.dart'; import 'package:todosupabase/screens/todo.dart'; @@ -29,7 +30,8 @@ class MyApp extends StatelessWidget { bodyText2: TextStyle(color: Colors.white), ), ), - initialRoute: "/", + initialRoute: + AuthSupabase.client.auth.currentSession != null ? "/todo" : "/", routes: { "/": (context) => const MyHomePage(), "/signup": (context) => const SignUpPage(), @@ -60,13 +62,19 @@ class MyHomePageState extends State { children: [ SvgPicture.asset("assets/supabase-dark.svg", width: 200), boxLarge(), - buttonCustom("Sign In", () { - Navigator.pushNamed(context, "/signin"); - }, false), + buttonCustom( + text: "Sign In", + onPressed: () { + Navigator.pushNamed(context, "/signin"); + }, + filled: false), boxSmall(), - buttonCustom("Sign Up", () { - Navigator.pushNamed(context, "/signup"); - }, false) + buttonCustom( + text: "Sign Up", + onPressed: () { + Navigator.pushNamed(context, "/signup"); + }, + filled: false) ], ), ), diff --git a/examples/todo-list/flutter-todo-list/lib/screens/signin.dart b/examples/todo-list/flutter-todo-list/lib/screens/signin.dart index ed46c061ccc..5ae1120a81f 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/signin.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/signin.dart @@ -49,22 +49,27 @@ class _SignInPageState extends State { child: CircularProgressIndicator(), ), ) - : buttonCustom("Sign In", () { - setState(() { - loading = true; - }); - AuthSupabase.loginUser( - email: _emailController.text, - password: _passwordController.text) - .then((value) { + : buttonCustom( + text: "Sign In", + onPressed: () async { + setState(() { + loading = true; + }); + var value = await AuthSupabase.loginUser( + email: _emailController.text, + password: _passwordController.text); setState(() { loading = false; }); if (value != null) { - Navigator.pushNamed(context, '/todo'); + Navigator.pushReplacementNamed(context, '/todo'); + } else { + snackbarAlert( + context: context, + message: "Invalid Email or Password"); } - }); - }, true) + }, + filled: true) ], ), ), diff --git a/examples/todo-list/flutter-todo-list/lib/screens/signup.dart b/examples/todo-list/flutter-todo-list/lib/screens/signup.dart index 1c72734b17f..8c3ef1e3d11 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/signup.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/signup.dart @@ -42,18 +42,20 @@ class _SignUpPageState extends State { _passwordController, ), boxSmall(), - buttonCustom("Sign Up", () { - AuthSupabase.createAccount( + buttonCustom( + text: "Sign Up", + onPressed: () async { + var value = await AuthSupabase.createAccount( email: _emailController.text, - password: _passwordController.text) - .then((value) { - if (value == true) { - CrudSupabase.addUser( - name: _nameController.text, email: _emailController.text); - Navigator.pushNamed(context, "/signin"); - } - }); - }, true) + password: _passwordController.text); + if (value == true) { + CrudSupabase.addUser( + name: _nameController.text, + email: _emailController.text); + Navigator.pushReplacementNamed(context, "/signin"); + } + }, + filled: true) ], ), ), diff --git a/examples/todo-list/flutter-todo-list/lib/screens/todo.dart b/examples/todo-list/flutter-todo-list/lib/screens/todo.dart index 6aefbdcecfc..4060b079bee 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/todo.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/todo.dart @@ -78,12 +78,13 @@ class _TodoPageState extends State { appBar: AppBar( backgroundColor: Colors.black, automaticallyImplyLeading: false, - title: Text('Hey ${userName.split(" ")[0]}, add todo'), + title: Text('Hey $userName, add todo'), actions: [ IconButton( icon: const Icon(Icons.exit_to_app), - onPressed: () { - AuthSupabase.logOut(context); + onPressed: () async { + await AuthSupabase.logOut(context); + Navigator.pop(context, '/'); }, ), ], From 9f1fb8005990b81664a4ce84e83a9cf994209f3f Mon Sep 17 00:00:00 2001 From: dshukertjr <18113850+dshukertjr@users.noreply.github.com> Date: Sat, 4 Jun 2022 17:56:35 +0900 Subject: [PATCH 05/57] fix: using outlinedButton instead of a custom button --- .../flutter-todo-list/lib/constant.dart | 40 +++---------------- .../todo-list/flutter-todo-list/lib/main.dart | 38 +++++++++++------- .../flutter-todo-list/lib/screens/signin.dart | 13 +++--- .../flutter-todo-list/lib/screens/signup.dart | 36 ++++++++--------- 4 files changed, 52 insertions(+), 75 deletions(-) diff --git a/examples/todo-list/flutter-todo-list/lib/constant.dart b/examples/todo-list/flutter-todo-list/lib/constant.dart index 9570c7f0dee..cda82944267 100644 --- a/examples/todo-list/flutter-todo-list/lib/constant.dart +++ b/examples/todo-list/flutter-todo-list/lib/constant.dart @@ -1,37 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -boxSmall() => const SizedBox(height: 20); -boxLarge() => const SizedBox(height: 40); - -Widget buttonCustom( - {required String text, - required Function() onPressed, - required bool filled}) { - return GestureDetector( - onTap: onPressed, - child: Container( - height: 50, - width: 300, - decoration: BoxDecoration( - color: filled ? Colors.white : Colors.transparent, - border: Border.all( - color: Colors.white, - width: 2, - ), - borderRadius: BorderRadius.circular(20), - ), - child: Center( - child: Text( - text, - style: TextStyle( - color: filled ? Colors.black : Colors.white, - fontWeight: FontWeight.bold), - ), - ), - ), - ); -} +const smallGap = SizedBox(height: 20); +const largeGap = SizedBox(height: 40); Widget textFeildCustom(String hintText, TextEditingController ctlr) { return SizedBox( @@ -74,7 +44,7 @@ Future displayTextInputDialog( context: context, builder: (context) { return AlertDialog( - title: Text('Add a todo'), + title: const Text('Add a todo'), content: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -89,7 +59,7 @@ Future displayTextInputDialog( decoration: const InputDecoration(hintText: "Add Due Date in DD/MM"), ), - boxSmall(), + smallGap, MaterialButton( onPressed: onTab, child: const Text("Add Task", @@ -97,7 +67,7 @@ Future displayTextInputDialog( color: Colors.white, fontWeight: FontWeight.bold, )), - color: Color(0xff33b27b), + color: const Color(0xff33b27b), ) ], ), diff --git a/examples/todo-list/flutter-todo-list/lib/main.dart b/examples/todo-list/flutter-todo-list/lib/main.dart index 19d00d10e7c..3080e75fd38 100644 --- a/examples/todo-list/flutter-todo-list/lib/main.dart +++ b/examples/todo-list/flutter-todo-list/lib/main.dart @@ -25,6 +25,16 @@ class MyApp extends StatelessWidget { theme: ThemeData.dark(), darkTheme: ThemeData( scaffoldBackgroundColor: Colors.black, + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + primary: Colors.white, + side: const BorderSide(color: Colors.white, width: 2), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + padding: const EdgeInsets.all(16), + ), + ), textTheme: const TextTheme( bodyText1: TextStyle(color: Colors.white), bodyText2: TextStyle(color: Colors.white), @@ -61,20 +71,20 @@ class MyHomePageState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ SvgPicture.asset("assets/supabase-dark.svg", width: 200), - boxLarge(), - buttonCustom( - text: "Sign In", - onPressed: () { - Navigator.pushNamed(context, "/signin"); - }, - filled: false), - boxSmall(), - buttonCustom( - text: "Sign Up", - onPressed: () { - Navigator.pushNamed(context, "/signup"); - }, - filled: false) + largeGap, + OutlinedButton( + child: const Text("Sign In"), + onPressed: () { + Navigator.pushNamed(context, "/signin"); + }, + ), + smallGap, + OutlinedButton( + child: const Text("Sign Up"), + onPressed: () { + Navigator.pushNamed(context, "/signup"); + }, + ) ], ), ), diff --git a/examples/todo-list/flutter-todo-list/lib/screens/signin.dart b/examples/todo-list/flutter-todo-list/lib/screens/signin.dart index 5ae1120a81f..e187080c2d6 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/signin.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/signin.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:todosupabase/constant.dart'; import 'package:todosupabase/functions/auth.dart'; -import 'package:todosupabase/functions/crud.dart'; class SignInPage extends StatefulWidget { const SignInPage({Key? key}) : super(key: key); @@ -26,17 +25,17 @@ class _SignInPageState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ SvgPicture.asset("assets/supabase-dark.svg", width: 200), - boxLarge(), + largeGap, textFeildCustom( "Email", _emailController, ), - boxSmall(), + smallGap, textFeildCustom( "Password", _passwordController, ), - boxSmall(), + smallGap, loading ? Container( height: 50, @@ -49,8 +48,8 @@ class _SignInPageState extends State { child: CircularProgressIndicator(), ), ) - : buttonCustom( - text: "Sign In", + : OutlinedButton( + child: const Text("Sign In"), onPressed: () async { setState(() { loading = true; @@ -69,7 +68,7 @@ class _SignInPageState extends State { message: "Invalid Email or Password"); } }, - filled: true) + ) ], ), ), diff --git a/examples/todo-list/flutter-todo-list/lib/screens/signup.dart b/examples/todo-list/flutter-todo-list/lib/screens/signup.dart index 8c3ef1e3d11..f185e473d2f 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/signup.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/signup.dart @@ -26,40 +26,38 @@ class _SignUpPageState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ SvgPicture.asset("assets/supabase-dark.svg", width: 200), - boxLarge(), + largeGap, textFeildCustom( "Full Name", _nameController, ), - boxSmall(), + smallGap, textFeildCustom( "Email", _emailController, ), - boxSmall(), + smallGap, textFeildCustom( "Password", _passwordController, ), - boxSmall(), - buttonCustom( - text: "Sign Up", - onPressed: () async { - var value = await AuthSupabase.createAccount( - email: _emailController.text, - password: _passwordController.text); - if (value == true) { - CrudSupabase.addUser( - name: _nameController.text, - email: _emailController.text); - Navigator.pushReplacementNamed(context, "/signin"); - } - }, - filled: true) + smallGap, + OutlinedButton( + child: const Text("Sign Up"), + onPressed: () async { + var value = await AuthSupabase.createAccount( + email: _emailController.text, + password: _passwordController.text); + if (value == true) { + CrudSupabase.addUser( + name: _nameController.text, email: _emailController.text); + Navigator.pushReplacementNamed(context, "/signin"); + } + }, + ) ], ), ), ); - ; } } From 2d9326db7c3885767f890ecab6781a812b1b09ec Mon Sep 17 00:00:00 2001 From: dshukertjr <18113850+dshukertjr@users.noreply.github.com> Date: Sat, 4 Jun 2022 18:05:59 +0900 Subject: [PATCH 06/57] fix: remove SizedBox that are directly under body of Scaffold --- .../todo-list/flutter-todo-list/lib/main.dart | 63 +++++++---- .../flutter-todo-list/lib/screens/signin.dart | 107 +++++++++--------- .../flutter-todo-list/lib/screens/signup.dart | 78 ++++++------- .../flutter-todo-list/lib/screens/todo.dart | 75 ++++++------ 4 files changed, 169 insertions(+), 154 deletions(-) diff --git a/examples/todo-list/flutter-todo-list/lib/main.dart b/examples/todo-list/flutter-todo-list/lib/main.dart index 3080e75fd38..83778d010c6 100644 --- a/examples/todo-list/flutter-todo-list/lib/main.dart +++ b/examples/todo-list/flutter-todo-list/lib/main.dart @@ -22,8 +22,7 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, - theme: ThemeData.dark(), - darkTheme: ThemeData( + theme: ThemeData.dark().copyWith( scaffoldBackgroundColor: Colors.black, outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom( @@ -35,6 +34,19 @@ class MyApp extends StatelessWidget { padding: const EdgeInsets.all(16), ), ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + primary: Colors.white, + onPrimary: Colors.black, + textStyle: const TextStyle( + fontWeight: FontWeight.bold, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + padding: const EdgeInsets.all(16), + ), + ), textTheme: const TextTheme( bodyText1: TextStyle(color: Colors.white), bodyText2: TextStyle(color: Colors.white), @@ -63,29 +75,30 @@ class MyHomePageState extends State { @override Widget build(BuildContext context) { return Scaffold( - body: SizedBox( - height: double.infinity, - width: double.infinity, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SvgPicture.asset("assets/supabase-dark.svg", width: 200), - largeGap, - OutlinedButton( - child: const Text("Sign In"), - onPressed: () { - Navigator.pushNamed(context, "/signin"); - }, - ), - smallGap, - OutlinedButton( - child: const Text("Sign Up"), - onPressed: () { - Navigator.pushNamed(context, "/signup"); - }, - ) - ], + body: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 300), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SvgPicture.asset("assets/supabase-dark.svg", width: 200), + largeGap, + OutlinedButton( + child: const Text("Sign In"), + onPressed: () { + Navigator.pushNamed(context, "/signin"); + }, + ), + smallGap, + OutlinedButton( + child: const Text("Sign Up"), + onPressed: () { + Navigator.pushNamed(context, "/signup"); + }, + ) + ], + ), ), ), ); diff --git a/examples/todo-list/flutter-todo-list/lib/screens/signin.dart b/examples/todo-list/flutter-todo-list/lib/screens/signin.dart index e187080c2d6..2d5531adaa0 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/signin.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/signin.dart @@ -17,59 +17,60 @@ class _SignInPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - body: SizedBox( - height: double.infinity, - width: double.infinity, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SvgPicture.asset("assets/supabase-dark.svg", width: 200), - largeGap, - textFeildCustom( - "Email", - _emailController, - ), - smallGap, - textFeildCustom( - "Password", - _passwordController, - ), - smallGap, - loading - ? Container( - height: 50, - width: 50, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(50), - ), - child: const Center( - child: CircularProgressIndicator(), - ), - ) - : OutlinedButton( - child: const Text("Sign In"), - onPressed: () async { - setState(() { - loading = true; - }); - var value = await AuthSupabase.loginUser( - email: _emailController.text, - password: _passwordController.text); - setState(() { - loading = false; - }); - if (value != null) { - Navigator.pushReplacementNamed(context, '/todo'); - } else { - snackbarAlert( - context: context, - message: "Invalid Email or Password"); - } - }, - ) - ], + body: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 300), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SvgPicture.asset("assets/supabase-dark.svg", width: 200), + largeGap, + textFeildCustom( + "Email", + _emailController, + ), + smallGap, + textFeildCustom( + "Password", + _passwordController, + ), + smallGap, + loading + ? Container( + height: 50, + width: 50, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(50), + ), + child: const Center( + child: CircularProgressIndicator(), + ), + ) + : ElevatedButton( + child: const Text("Sign In"), + onPressed: () async { + setState(() { + loading = true; + }); + var value = await AuthSupabase.loginUser( + email: _emailController.text, + password: _passwordController.text); + setState(() { + loading = false; + }); + if (value != null) { + Navigator.pushReplacementNamed(context, '/todo'); + } else { + snackbarAlert( + context: context, + message: "Invalid Email or Password"); + } + }, + ) + ], + ), ), ), ); diff --git a/examples/todo-list/flutter-todo-list/lib/screens/signup.dart b/examples/todo-list/flutter-todo-list/lib/screens/signup.dart index f185e473d2f..7c7bdfd6a90 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/signup.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/signup.dart @@ -18,44 +18,46 @@ class _SignUpPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - body: SizedBox( - height: double.infinity, - width: double.infinity, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SvgPicture.asset("assets/supabase-dark.svg", width: 200), - largeGap, - textFeildCustom( - "Full Name", - _nameController, - ), - smallGap, - textFeildCustom( - "Email", - _emailController, - ), - smallGap, - textFeildCustom( - "Password", - _passwordController, - ), - smallGap, - OutlinedButton( - child: const Text("Sign Up"), - onPressed: () async { - var value = await AuthSupabase.createAccount( - email: _emailController.text, - password: _passwordController.text); - if (value == true) { - CrudSupabase.addUser( - name: _nameController.text, email: _emailController.text); - Navigator.pushReplacementNamed(context, "/signin"); - } - }, - ) - ], + body: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 300), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SvgPicture.asset("assets/supabase-dark.svg", width: 200), + largeGap, + textFeildCustom( + "Full Name", + _nameController, + ), + smallGap, + textFeildCustom( + "Email", + _emailController, + ), + smallGap, + textFeildCustom( + "Password", + _passwordController, + ), + smallGap, + ElevatedButton( + child: const Text("Sign Up"), + onPressed: () async { + var value = await AuthSupabase.createAccount( + email: _emailController.text, + password: _passwordController.text); + if (value == true) { + CrudSupabase.addUser( + name: _nameController.text, + email: _emailController.text); + Navigator.pushReplacementNamed(context, "/signin"); + } + }, + ) + ], + ), ), ), ); diff --git a/examples/todo-list/flutter-todo-list/lib/screens/todo.dart b/examples/todo-list/flutter-todo-list/lib/screens/todo.dart index 4060b079bee..7d5f56f6769 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/todo.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/todo.dart @@ -24,9 +24,9 @@ class _TodoPageState extends State { userName = nameVal[0]['name']; }); List value = await CrudSupabase.getTodo(); - value.forEach((element) { + for (var element in value) { todos.add(element); - }); + } setState(() { loading = false; }); @@ -34,9 +34,8 @@ class _TodoPageState extends State { @override void initState() { - // TODO: implement initState - super.initState(); initFunc(); + super.initState(); } @override @@ -49,32 +48,6 @@ class _TodoPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - floatingActionButton: FloatingActionButton( - backgroundColor: const Color(0xff33b27b), - onPressed: () { - displayTextInputDialog(context, () async { - await CrudSupabase.addTodo( - task: _taskEditingController.text, - due: _dueEditingController.text, - ); - Navigator.pop(context); - setState(() { - loading = true; - }); - - List value = await CrudSupabase.getTodo(); - todos.clear(); - setState(() {}); - value.forEach((element) { - todos.add(element); - }); - setState(() { - loading = false; - }); - }, _taskEditingController, _dueEditingController); - }, - child: const Icon(Icons.add), - ), appBar: AppBar( backgroundColor: Colors.black, automaticallyImplyLeading: false, @@ -117,9 +90,9 @@ class _TodoPageState extends State { await CrudSupabase.getTodo(); todos.clear(); setState(() {}); - value.forEach((element) { + for (var element in value) { todos.add(element); - }); + } setState(() { loading = false; }); @@ -127,11 +100,11 @@ class _TodoPageState extends State { }, title: Text( todos[index]["task"], - style: TextStyle(color: Colors.white), + style: const TextStyle(color: Colors.white), ), subtitle: Text( todos[index]["due"], - style: TextStyle(color: Colors.white), + style: const TextStyle(color: Colors.white), ), leading: GestureDetector( onTap: () async { @@ -144,9 +117,9 @@ class _TodoPageState extends State { await CrudSupabase.getTodo(); todos.clear(); setState(() {}); - value.forEach((element) { + for (var element in value) { todos.add(element); - }); + } setState(() { loading = false; }); @@ -168,9 +141,9 @@ class _TodoPageState extends State { await CrudSupabase.getTodo(); todos.clear(); setState(() {}); - value.forEach((element) { + for (var element in value) { todos.add(element); - }); + } setState(() { loading = false; }); @@ -202,6 +175,32 @@ class _TodoPageState extends State { ); }, )), + floatingActionButton: FloatingActionButton( + backgroundColor: const Color(0xff33b27b), + onPressed: () { + displayTextInputDialog(context, () async { + await CrudSupabase.addTodo( + task: _taskEditingController.text, + due: _dueEditingController.text, + ); + Navigator.pop(context); + setState(() { + loading = true; + }); + + List value = await CrudSupabase.getTodo(); + todos.clear(); + setState(() {}); + for (var element in value) { + todos.add(element); + } + setState(() { + loading = false; + }); + }, _taskEditingController, _dueEditingController); + }, + child: const Icon(Icons.add), + ), ); } } From fbf099c67fd204a30396fa22d5ad2fba310f078c Mon Sep 17 00:00:00 2001 From: dshukertjr <18113850+dshukertjr@users.noreply.github.com> Date: Sun, 5 Jun 2022 12:49:33 +0900 Subject: [PATCH 07/57] feat: updated input theme --- .../ios/Flutter/Debug.xcconfig | 1 + .../ios/Flutter/Release.xcconfig | 1 + .../todo-list/flutter-todo-list/ios/Podfile | 41 +++++++++++++++++++ .../flutter-todo-list/lib/functions/auth.dart | 12 ++++-- .../flutter-todo-list/lib/functions/crud.dart | 6 +-- .../todo-list/flutter-todo-list/lib/main.dart | 10 +++++ .../flutter-todo-list/lib/screens/signup.dart | 12 ++++-- .../windows/flutter/generated_plugins.cmake | 8 ++++ 8 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 examples/todo-list/flutter-todo-list/ios/Podfile diff --git a/examples/todo-list/flutter-todo-list/ios/Flutter/Debug.xcconfig b/examples/todo-list/flutter-todo-list/ios/Flutter/Debug.xcconfig index 592ceee85b8..ec97fc6f302 100644 --- a/examples/todo-list/flutter-todo-list/ios/Flutter/Debug.xcconfig +++ b/examples/todo-list/flutter-todo-list/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/examples/todo-list/flutter-todo-list/ios/Flutter/Release.xcconfig b/examples/todo-list/flutter-todo-list/ios/Flutter/Release.xcconfig index 592ceee85b8..c4855bfe200 100644 --- a/examples/todo-list/flutter-todo-list/ios/Flutter/Release.xcconfig +++ b/examples/todo-list/flutter-todo-list/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/examples/todo-list/flutter-todo-list/ios/Podfile b/examples/todo-list/flutter-todo-list/ios/Podfile new file mode 100644 index 00000000000..1e8c3c90a55 --- /dev/null +++ b/examples/todo-list/flutter-todo-list/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/examples/todo-list/flutter-todo-list/lib/functions/auth.dart b/examples/todo-list/flutter-todo-list/lib/functions/auth.dart index 62e5d88b425..14ff177ca9a 100644 --- a/examples/todo-list/flutter-todo-list/lib/functions/auth.dart +++ b/examples/todo-list/flutter-todo-list/lib/functions/auth.dart @@ -3,8 +3,10 @@ import 'package:supabase_flutter/supabase_flutter.dart'; class AuthSupabase { static final client = Supabase.instance.client; - static Future createAccount( - {required String email, required String password}) async { + static Future createAccount({ + required String email, + required String password, + }) async { final res = await client.auth.signUp(email, password); final error = res.error; if (error == null) { @@ -14,8 +16,10 @@ class AuthSupabase { } } - static Future loginUser( - {required String email, required String password}) async { + static Future loginUser({ + required String email, + required String password, + }) async { final res = await client.auth.signIn(email: email, password: password); final user = res.data?.user; return user?.id; diff --git a/examples/todo-list/flutter-todo-list/lib/functions/crud.dart b/examples/todo-list/flutter-todo-list/lib/functions/crud.dart index fed4b0aa6ca..31ca5b2cee8 100644 --- a/examples/todo-list/flutter-todo-list/lib/functions/crud.dart +++ b/examples/todo-list/flutter-todo-list/lib/functions/crud.dart @@ -1,9 +1,9 @@ -import 'dart:convert'; - import 'package:todosupabase/functions/auth.dart'; class CrudSupabase { - static Future getUser({required String email}) async { + static Future getUser({ + required String email, + }) async { final res = await AuthSupabase.client .from('Users') .select() diff --git a/examples/todo-list/flutter-todo-list/lib/main.dart b/examples/todo-list/flutter-todo-list/lib/main.dart index 83778d010c6..4dc62fc9f5d 100644 --- a/examples/todo-list/flutter-todo-list/lib/main.dart +++ b/examples/todo-list/flutter-todo-list/lib/main.dart @@ -47,6 +47,16 @@ class MyApp extends StatelessWidget { padding: const EdgeInsets.all(16), ), ), + inputDecorationTheme: InputDecorationTheme( + labelStyle: const TextStyle(color: Colors.white), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide( + color: Colors.white, + width: 2, + ), + ), + ), textTheme: const TextTheme( bodyText1: TextStyle(color: Colors.white), bodyText2: TextStyle(color: Colors.white), diff --git a/examples/todo-list/flutter-todo-list/lib/screens/signup.dart b/examples/todo-list/flutter-todo-list/lib/screens/signup.dart index 7c7bdfd6a90..86189650790 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/signup.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/signup.dart @@ -27,10 +27,16 @@ class _SignUpPageState extends State { children: [ SvgPicture.asset("assets/supabase-dark.svg", width: 200), largeGap, - textFeildCustom( - "Full Name", - _nameController, + TextFormField( + controller: _nameController, + decoration: const InputDecoration( + label: Text('Full Name'), + ), ), + // textFeildCustom( + // "Full Name", + // _nameController, + // ), smallGap, textFeildCustom( "Email", diff --git a/examples/todo-list/flutter-todo-list/windows/flutter/generated_plugins.cmake b/examples/todo-list/flutter-todo-list/windows/flutter/generated_plugins.cmake index 411af46dd72..88b22e5c775 100644 --- a/examples/todo-list/flutter-todo-list/windows/flutter/generated_plugins.cmake +++ b/examples/todo-list/flutter-todo-list/windows/flutter/generated_plugins.cmake @@ -6,6 +6,9 @@ list(APPEND FLUTTER_PLUGIN_LIST url_launcher_windows ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -14,3 +17,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) From 50901c9a7d76fc871dbddb0aeecc92d4237972fe Mon Sep 17 00:00:00 2001 From: dshukertjr <18113850+dshukertjr@users.noreply.github.com> Date: Sun, 5 Jun 2022 15:50:53 +0900 Subject: [PATCH 08/57] fix: replacing custom textfield with themed TextFormField --- .../flutter-todo-list/lib/constant.dart | 31 ------------------- .../todo-list/flutter-todo-list/lib/main.dart | 13 +++++++- .../flutter-todo-list/lib/screens/signin.dart | 14 +++++---- .../flutter-todo-list/lib/screens/signup.dart | 22 +++++++------ 4 files changed, 32 insertions(+), 48 deletions(-) diff --git a/examples/todo-list/flutter-todo-list/lib/constant.dart b/examples/todo-list/flutter-todo-list/lib/constant.dart index cda82944267..e26d371574d 100644 --- a/examples/todo-list/flutter-todo-list/lib/constant.dart +++ b/examples/todo-list/flutter-todo-list/lib/constant.dart @@ -3,37 +3,6 @@ import 'package:flutter/material.dart'; const smallGap = SizedBox(height: 20); const largeGap = SizedBox(height: 40); -Widget textFeildCustom(String hintText, TextEditingController ctlr) { - return SizedBox( - height: 70, - width: 300, - child: TextField( - controller: ctlr, - style: const TextStyle(color: Colors.white), - decoration: InputDecoration( - hintText: hintText, - hintStyle: const TextStyle( - color: Colors.white, - ), - enabledBorder: OutlineInputBorder( - borderSide: const BorderSide( - color: Colors.white, - width: 2, - ), - borderRadius: BorderRadius.circular(20), - ), - focusedBorder: OutlineInputBorder( - borderSide: const BorderSide( - color: Colors.white, - width: 2, - ), - borderRadius: BorderRadius.circular(20), - ), - ), - ), - ); -} - Future displayTextInputDialog( BuildContext context, Function()? onTab, diff --git a/examples/todo-list/flutter-todo-list/lib/main.dart b/examples/todo-list/flutter-todo-list/lib/main.dart index 4dc62fc9f5d..7186243bc97 100644 --- a/examples/todo-list/flutter-todo-list/lib/main.dart +++ b/examples/todo-list/flutter-todo-list/lib/main.dart @@ -49,13 +49,24 @@ class MyApp extends StatelessWidget { ), inputDecorationTheme: InputDecorationTheme( labelStyle: const TextStyle(color: Colors.white), - border: OutlineInputBorder( + focusColor: Colors.green, + enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide( color: Colors.white, width: 2, ), ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide( + color: Colors.green, + width: 2, + ), + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), ), textTheme: const TextTheme( bodyText1: TextStyle(color: Colors.white), diff --git a/examples/todo-list/flutter-todo-list/lib/screens/signin.dart b/examples/todo-list/flutter-todo-list/lib/screens/signin.dart index 2d5531adaa0..3a09f80c355 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/signin.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/signin.dart @@ -26,14 +26,16 @@ class _SignInPageState extends State { children: [ SvgPicture.asset("assets/supabase-dark.svg", width: 200), largeGap, - textFeildCustom( - "Email", - _emailController, + TextFormField( + controller: _emailController, + decoration: const InputDecoration(label: Text('Email')), + keyboardType: TextInputType.emailAddress, ), smallGap, - textFeildCustom( - "Password", - _passwordController, + TextFormField( + controller: _emailController, + decoration: const InputDecoration(label: Text('Password')), + obscureText: true, ), smallGap, loading diff --git a/examples/todo-list/flutter-todo-list/lib/screens/signup.dart b/examples/todo-list/flutter-todo-list/lib/screens/signup.dart index 86189650790..55e2baf9db1 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/signup.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/signup.dart @@ -33,19 +33,21 @@ class _SignUpPageState extends State { label: Text('Full Name'), ), ), - // textFeildCustom( - // "Full Name", - // _nameController, - // ), smallGap, - textFeildCustom( - "Email", - _emailController, + TextFormField( + controller: _emailController, + decoration: const InputDecoration( + label: Text('Full Name'), + ), + keyboardType: TextInputType.emailAddress, ), smallGap, - textFeildCustom( - "Password", - _passwordController, + TextFormField( + controller: _passwordController, + decoration: const InputDecoration( + label: Text('Password'), + ), + obscureText: true, ), smallGap, ElevatedButton( From 0eb7ab19927414ee9cdb8a69cedbb801d5d4f2f0 Mon Sep 17 00:00:00 2001 From: dshukertjr <18113850+dshukertjr@users.noreply.github.com> Date: Sun, 5 Jun 2022 16:00:08 +0900 Subject: [PATCH 09/57] fix: replace snackbar method with extension method --- .../flutter-todo-list/lib/constant.dart | 70 ++++++++++++++++--- .../todo-list/flutter-todo-list/lib/main.dart | 52 +------------- .../flutter-todo-list/lib/screens/signin.dart | 5 +- .../flutter-todo-list/lib/screens/todo.dart | 2 +- 4 files changed, 66 insertions(+), 63 deletions(-) diff --git a/examples/todo-list/flutter-todo-list/lib/constant.dart b/examples/todo-list/flutter-todo-list/lib/constant.dart index e26d371574d..7209c2c3ec9 100644 --- a/examples/todo-list/flutter-todo-list/lib/constant.dart +++ b/examples/todo-list/flutter-todo-list/lib/constant.dart @@ -3,6 +3,58 @@ import 'package:flutter/material.dart'; const smallGap = SizedBox(height: 20); const largeGap = SizedBox(height: 40); +final appTheme = ThemeData.dark().copyWith( + scaffoldBackgroundColor: Colors.black, + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + primary: Colors.white, + side: const BorderSide(color: Colors.white, width: 2), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + padding: const EdgeInsets.all(16), + ), + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + primary: Colors.white, + onPrimary: Colors.black, + textStyle: const TextStyle( + fontWeight: FontWeight.bold, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + padding: const EdgeInsets.all(16), + ), + ), + inputDecorationTheme: InputDecorationTheme( + labelStyle: const TextStyle(color: Colors.white), + focusColor: Colors.green, + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide( + color: Colors.white, + width: 2, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide( + color: Colors.green, + width: 2, + ), + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + textTheme: const TextTheme( + bodyText1: TextStyle(color: Colors.white), + bodyText2: TextStyle(color: Colors.white), + ), +); + Future displayTextInputDialog( BuildContext context, Function()? onTab, @@ -44,13 +96,15 @@ Future displayTextInputDialog( }); } -ScaffoldFeatureController snackbarAlert( - {required BuildContext context, required String message}) { - return ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(message), - duration: const Duration(seconds: 2), +extension ShowSnackBar on BuildContext { + /// Extention method to easily display error snack bar. + void showErrorSnackbar(String text) { + ScaffoldMessenger.of(this).showSnackBar(SnackBar( + content: Text( + text, + style: const TextStyle(color: Color(0xFFFFFFFF)), + ), backgroundColor: Colors.red, - ), - ); + )); + } } diff --git a/examples/todo-list/flutter-todo-list/lib/main.dart b/examples/todo-list/flutter-todo-list/lib/main.dart index 7186243bc97..0aaa0b1b843 100644 --- a/examples/todo-list/flutter-todo-list/lib/main.dart +++ b/examples/todo-list/flutter-todo-list/lib/main.dart @@ -22,57 +22,7 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, - theme: ThemeData.dark().copyWith( - scaffoldBackgroundColor: Colors.black, - outlinedButtonTheme: OutlinedButtonThemeData( - style: OutlinedButton.styleFrom( - primary: Colors.white, - side: const BorderSide(color: Colors.white, width: 2), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - padding: const EdgeInsets.all(16), - ), - ), - elevatedButtonTheme: ElevatedButtonThemeData( - style: ElevatedButton.styleFrom( - primary: Colors.white, - onPrimary: Colors.black, - textStyle: const TextStyle( - fontWeight: FontWeight.bold, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - padding: const EdgeInsets.all(16), - ), - ), - inputDecorationTheme: InputDecorationTheme( - labelStyle: const TextStyle(color: Colors.white), - focusColor: Colors.green, - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide( - color: Colors.white, - width: 2, - ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide( - color: Colors.green, - width: 2, - ), - ), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - textTheme: const TextTheme( - bodyText1: TextStyle(color: Colors.white), - bodyText2: TextStyle(color: Colors.white), - ), - ), + theme: appTheme, initialRoute: AuthSupabase.client.auth.currentSession != null ? "/todo" : "/", routes: { diff --git a/examples/todo-list/flutter-todo-list/lib/screens/signin.dart b/examples/todo-list/flutter-todo-list/lib/screens/signin.dart index 3a09f80c355..80783e1024b 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/signin.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/signin.dart @@ -65,9 +65,8 @@ class _SignInPageState extends State { if (value != null) { Navigator.pushReplacementNamed(context, '/todo'); } else { - snackbarAlert( - context: context, - message: "Invalid Email or Password"); + context + .showErrorSnackbar('Invalid Email or Password'); } }, ) diff --git a/examples/todo-list/flutter-todo-list/lib/screens/todo.dart b/examples/todo-list/flutter-todo-list/lib/screens/todo.dart index 7d5f56f6769..df06b42f166 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/todo.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/todo.dart @@ -11,7 +11,7 @@ class TodoPage extends StatefulWidget { } class _TodoPageState extends State { - String userName = ""; + String userName = ''; bool loading = true; List todos = []; final TextEditingController _taskEditingController = TextEditingController(); From eaa5aa5a61e1281828e8b7bdc2cc49854a463c2e Mon Sep 17 00:00:00 2001 From: dshukertjr <18113850+dshukertjr@users.noreply.github.com> Date: Sun, 5 Jun 2022 16:42:54 +0900 Subject: [PATCH 10/57] fix: lint cleanup --- .../todo-list/flutter-todo-list/README.md | 65 ++++++- .../flutter-todo-list/analysis_options.yaml | 29 +--- .../flutter-todo-list/lib/constant.dart | 59 ++----- .../flutter-todo-list/lib/functions/crud.dart | 53 +++--- .../flutter-todo-list/lib/screens/signin.dart | 12 +- .../flutter-todo-list/lib/screens/signup.dart | 23 +-- .../flutter-todo-list/lib/screens/todo.dart | 163 ++++++++++++------ .../todo-list/flutter-todo-list/pubspec.lock | 29 ++-- .../todo-list/flutter-todo-list/pubspec.yaml | 2 +- 9 files changed, 253 insertions(+), 182 deletions(-) diff --git a/examples/todo-list/flutter-todo-list/README.md b/examples/todo-list/flutter-todo-list/README.md index 8f1af2000ca..117838af68f 100644 --- a/examples/todo-list/flutter-todo-list/README.md +++ b/examples/todo-list/flutter-todo-list/README.md @@ -1 +1,64 @@ -A Flutter Supabase CRUD App +# Flutter Todo example using Supabase + +## Build from scratch + +### 1. Create new project + +Sign up to Supabase - [https://app.supabase.com](https://app.supabase.com) and create a new project. Wait for your database to start. + +### 2. Run "Todo List" Quickstart + +Once your database has started, run the "Todo List" quickstart. Inside of your project, enter the `SQL editor` tab and scroll down until you see `TODO LIST: Build a basic todo list with Row Level Security`. + +### 3. Get the URL and Key + +Go to the Project Settings (the cog icon), open the API tab, and find your API URL and `anon` key, you'll need these in the next step. + +The `anon` key is your client-side API key. It allows "anonymous access" to your database, until the user has logged in. Once they have logged in, the keys will switch to the user's own login token. This enables row level security for your data. Read more about this [below](#postgres-row-level-security). + +![image](https://user-images.githubusercontent.com/10214025/88916245-528c2680-d298-11ea-8a71-708f93e1ce4f.png) + +**_NOTE_**: The `service_role` key has full access to your data, bypassing any security policies. These keys have to be kept secret and are meant to be used in server environments and never on a client or browser. + +## Supabase details + +### Postgres Row level security + +This project uses very high-level Authorization using Postgres' Role Level Security. +When you start a Postgres database on Supabase, we populate it with an `auth` schema, and some helper functions. +When a user logs in, they are issued a JWT with the role `authenticated` and their UUID. +We can use these details to provide fine-grained control over what each user can and cannot do. + +This is a trimmed-down schema, with the policies: + +```sql +create table todos ( + id bigint generated by default as identity primary key, + user_id uuid references auth.users not null, + task text check (char_length(task) > 3), + is_complete boolean default false, + inserted_at timestamp with time zone default timezone('utc'::text, now()) not null +); + +alter table todos enable row level security; + +create policy "Individuals can create todos." on todos for + insert with check (auth.uid() = user_id); + +create policy "Individuals can view their own todos. " on todos for + select using (auth.uid() = user_id); + +create policy "Individuals can update their own todos." on todos for + update using (auth.uid() = user_id); + +create policy "Individuals can delete their own todos." on todos for + delete using (auth.uid() = user_id); +``` + +## Authors + +- [Aniket Singh](https://github.com/Anikets08) +- [Tyler Shukert](https://github.com/dshukertjr) +- [Supabase](https://supabase.com) + +Supabase is open source. We'd love for you to follow along and get involved at https://github.com/supabase/supabase diff --git a/examples/todo-list/flutter-todo-list/analysis_options.yaml b/examples/todo-list/flutter-todo-list/analysis_options.yaml index 61b6c4de17c..dd058571417 100644 --- a/examples/todo-list/flutter-todo-list/analysis_options.yaml +++ b/examples/todo-list/flutter-todo-list/analysis_options.yaml @@ -1,29 +1,6 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml +include: package:lints/recommended.yaml linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at - # https://dart-lang.github.io/linter/lints/index.html. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options + prefer_single_quotes: true + require_trailing_commas: true \ No newline at end of file diff --git a/examples/todo-list/flutter-todo-list/lib/constant.dart b/examples/todo-list/flutter-todo-list/lib/constant.dart index 7209c2c3ec9..ef3ef3651cd 100644 --- a/examples/todo-list/flutter-todo-list/lib/constant.dart +++ b/examples/todo-list/flutter-todo-list/lib/constant.dart @@ -5,6 +5,10 @@ const largeGap = SizedBox(height: 40); final appTheme = ThemeData.dark().copyWith( scaffoldBackgroundColor: Colors.black, + appBarTheme: const AppBarTheme( + backgroundColor: Colors.transparent, + elevation: 0, + ), outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom( primary: Colors.white, @@ -55,56 +59,17 @@ final appTheme = ThemeData.dark().copyWith( ), ); -Future displayTextInputDialog( - BuildContext context, - Function()? onTab, - TextEditingController taskCtrl, - TextEditingController dueCtrl, -) async { - return showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text('Add a todo'), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - TextField( - controller: taskCtrl, - decoration: - const InputDecoration(hintText: "Add title of todo"), - ), - TextField( - controller: dueCtrl, - keyboardType: TextInputType.datetime, - decoration: - const InputDecoration(hintText: "Add Due Date in DD/MM"), - ), - smallGap, - MaterialButton( - onPressed: onTab, - child: const Text("Add Task", - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - )), - color: const Color(0xff33b27b), - ) - ], - ), - ); - }); -} - extension ShowSnackBar on BuildContext { /// Extention method to easily display error snack bar. void showErrorSnackbar(String text) { - ScaffoldMessenger.of(this).showSnackBar(SnackBar( - content: Text( - text, - style: const TextStyle(color: Color(0xFFFFFFFF)), + ScaffoldMessenger.of(this).showSnackBar( + SnackBar( + content: Text( + text, + style: const TextStyle(color: Color(0xFFFFFFFF)), + ), + backgroundColor: Colors.red, ), - backgroundColor: Colors.red, - )); + ); } } diff --git a/examples/todo-list/flutter-todo-list/lib/functions/crud.dart b/examples/todo-list/flutter-todo-list/lib/functions/crud.dart index 31ca5b2cee8..27ce0f63cfe 100644 --- a/examples/todo-list/flutter-todo-list/lib/functions/crud.dart +++ b/examples/todo-list/flutter-todo-list/lib/functions/crud.dart @@ -7,7 +7,7 @@ class CrudSupabase { final res = await AuthSupabase.client .from('Users') .select() - .eq("email", email) + .eq('email', email) .execute(); final data = res.data; final error = res.error; @@ -19,20 +19,22 @@ class CrudSupabase { final res = await AuthSupabase.client .from('Todo') .select() - .eq("email", AuthSupabase.client.auth.currentUser!.email.toString()) + .eq('email', AuthSupabase.client.auth.currentUser!.email.toString()) .execute(); final data = res.data; final error = res.error; return data; } - static Future addTodo( - {required String task, required String due}) async { + static Future addTodo({ + required String task, + required String due, + }) async { final res = await AuthSupabase.client.from('Todo').insert({ - "email": AuthSupabase.client.auth.currentUser!.email.toString(), - "task": task, - "due": due, - "done": false + 'email': AuthSupabase.client.auth.currentUser!.email.toString(), + 'task': task, + 'due': due, + 'done': false }).execute(); final data = res.data; final error = res.error; @@ -42,19 +44,21 @@ class CrudSupabase { static Future deleteTodo({required String id}) async { final res = - await AuthSupabase.client.from('Todo').delete().eq("id", id).execute(); + await AuthSupabase.client.from('Todo').delete().eq('id', id).execute(); final data = res.data; final error = res.error; print(data); return data; } - static Future taskDone( - {required String id, required bool value}) async { + static Future taskDone({ + required String id, + required bool value, + }) async { final res = await AuthSupabase.client .from('Todo') - .update({"done": value}) - .eq("id", id) + .update({'done': value}) + .eq('id', id) .execute(); final data = res.data; final error = res.error; @@ -62,12 +66,15 @@ class CrudSupabase { return data; } - static Future editTask( - {required String id, required String due, required String task}) async { + static Future editTask({ + required String id, + required String due, + required String task, + }) async { final res = await AuthSupabase.client .from('Todo') - .update({"due": due, "task": task}) - .eq("id", id) + .update({'due': due, 'task': task}) + .eq('id', id) .execute(); final data = res.data; final error = res.error; @@ -75,13 +82,15 @@ class CrudSupabase { return data; } - static Future addUser( - {required String name, required String email}) async { + static Future addUser({ + required String name, + required String email, + }) async { final res = await AuthSupabase.client.from('Users').insert({ - "name": name, - "email": email, + 'name': name, + 'email': email, }).execute(); - print("adding number"); + print('adding number'); final data = res.data; final error = res.error; print(data); diff --git a/examples/todo-list/flutter-todo-list/lib/screens/signin.dart b/examples/todo-list/flutter-todo-list/lib/screens/signin.dart index 80783e1024b..6490c54f179 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/signin.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/signin.dart @@ -17,6 +17,7 @@ class _SignInPageState extends State { @override Widget build(BuildContext context) { return Scaffold( + appBar: AppBar(), body: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 300), @@ -24,7 +25,7 @@ class _SignInPageState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - SvgPicture.asset("assets/supabase-dark.svg", width: 200), + SvgPicture.asset('assets/supabase-dark.svg', width: 200), largeGap, TextFormField( controller: _emailController, @@ -33,7 +34,7 @@ class _SignInPageState extends State { ), smallGap, TextFormField( - controller: _emailController, + controller: _passwordController, decoration: const InputDecoration(label: Text('Password')), obscureText: true, ), @@ -51,14 +52,15 @@ class _SignInPageState extends State { ), ) : ElevatedButton( - child: const Text("Sign In"), + child: const Text('Sign In'), onPressed: () async { setState(() { loading = true; }); var value = await AuthSupabase.loginUser( - email: _emailController.text, - password: _passwordController.text); + email: _emailController.text, + password: _passwordController.text, + ); setState(() { loading = false; }); diff --git a/examples/todo-list/flutter-todo-list/lib/screens/signup.dart b/examples/todo-list/flutter-todo-list/lib/screens/signup.dart index 55e2baf9db1..baca47c60d9 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/signup.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/signup.dart @@ -18,6 +18,7 @@ class _SignUpPageState extends State { @override Widget build(BuildContext context) { return Scaffold( + appBar: AppBar(), body: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 300), @@ -25,19 +26,19 @@ class _SignUpPageState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - SvgPicture.asset("assets/supabase-dark.svg", width: 200), + SvgPicture.asset('assets/supabase-dark.svg', width: 200), largeGap, TextFormField( controller: _nameController, decoration: const InputDecoration( - label: Text('Full Name'), + label: Text('Username'), ), ), smallGap, TextFormField( controller: _emailController, decoration: const InputDecoration( - label: Text('Full Name'), + label: Text('Email'), ), keyboardType: TextInputType.emailAddress, ), @@ -51,16 +52,18 @@ class _SignUpPageState extends State { ), smallGap, ElevatedButton( - child: const Text("Sign Up"), + child: const Text('Sign Up'), onPressed: () async { - var value = await AuthSupabase.createAccount( - email: _emailController.text, - password: _passwordController.text); + final value = await AuthSupabase.createAccount( + email: _emailController.text, + password: _passwordController.text, + ); if (value == true) { CrudSupabase.addUser( - name: _nameController.text, - email: _emailController.text); - Navigator.pushReplacementNamed(context, "/signin"); + name: _nameController.text, + email: _emailController.text, + ); + Navigator.pushReplacementNamed(context, '/signin'); } }, ) diff --git a/examples/todo-list/flutter-todo-list/lib/screens/todo.dart b/examples/todo-list/flutter-todo-list/lib/screens/todo.dart index df06b42f166..23f0c9a8163 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/todo.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/todo.dart @@ -17,9 +17,10 @@ class _TodoPageState extends State { final TextEditingController _taskEditingController = TextEditingController(); final TextEditingController _dueEditingController = TextEditingController(); - void initFunc() async { + Future initFunc() async { var nameVal = await CrudSupabase.getUser( - email: AuthSupabase.client.auth.currentUser!.email.toString()); + email: AuthSupabase.client.auth.currentUser!.email.toString(), + ); setState(() { userName = nameVal[0]['name']; }); @@ -45,6 +46,50 @@ class _TodoPageState extends State { _dueEditingController.dispose(); } + Future displayTextInputDialog({ + required BuildContext context, + void Function()? onTab, + required TextEditingController taskCtrl, + required TextEditingController dueCtrl, + }) async { + return showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('Add a todo'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: taskCtrl, + decoration: + const InputDecoration(hintText: 'Add title of todo'), + ), + TextField( + controller: dueCtrl, + keyboardType: TextInputType.datetime, + decoration: + const InputDecoration(hintText: 'Add Due Date in DD/MM'), + ), + smallGap, + MaterialButton( + onPressed: onTab, + child: const Text( + 'Add Task', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + color: const Color(0xff33b27b), + ) + ], + ), + ); + }, + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -69,7 +114,7 @@ class _TodoPageState extends State { width: double.infinity, child: todos.isEmpty ? const Center( - child: Text("No Task, add new task"), + child: Text('No Task, add new task'), ) : ListView.builder( itemCount: todos.length, @@ -77,33 +122,39 @@ class _TodoPageState extends State { return ListTile( //Long Press to edit onLongPress: () { - displayTextInputDialog(context, () async { - await CrudSupabase.editTask( - id: todos[index]["id"].toString(), + displayTextInputDialog( + context: context, + taskCtrl: _taskEditingController, + dueCtrl: _dueEditingController, + onTab: () async { + await CrudSupabase.editTask( + id: todos[index]['id'].toString(), due: _dueEditingController.text, - task: _taskEditingController.text); - Navigator.pop(context); - setState(() { - loading = true; - }); - List value = - await CrudSupabase.getTodo(); - todos.clear(); - setState(() {}); - for (var element in value) { - todos.add(element); - } - setState(() { - loading = false; - }); - }, _taskEditingController, _dueEditingController); + task: _taskEditingController.text, + ); + Navigator.pop(context); + setState(() { + loading = true; + }); + List value = + await CrudSupabase.getTodo(); + todos.clear(); + setState(() {}); + for (var element in value) { + todos.add(element); + } + setState(() { + loading = false; + }); + }, + ); }, title: Text( - todos[index]["task"], + todos[index]['task'], style: const TextStyle(color: Colors.white), ), subtitle: Text( - todos[index]["due"], + todos[index]['due'], style: const TextStyle(color: Colors.white), ), leading: GestureDetector( @@ -112,7 +163,8 @@ class _TodoPageState extends State { loading = true; }); await CrudSupabase.deleteTodo( - id: todos[index]["id"].toString()); + id: todos[index]['id'].toString(), + ); List value = await CrudSupabase.getTodo(); todos.clear(); @@ -135,8 +187,9 @@ class _TodoPageState extends State { loading = true; }); await CrudSupabase.taskDone( - id: todos[index]["id"].toString(), - value: !todos[index]["done"]); + id: todos[index]['id'].toString(), + value: !todos[index]['done'], + ); List value = await CrudSupabase.getTodo(); todos.clear(); @@ -156,12 +209,12 @@ class _TodoPageState extends State { color: const Color(0xff33b27b), width: 2, ), - color: todos[index]["done"] + color: todos[index]['done'] ? const Color(0xff33b27b) : Colors.transparent, borderRadius: BorderRadius.circular(5), ), - child: todos[index]["done"] + child: todos[index]['done'] ? const Center( child: Icon( Icons.done, @@ -174,32 +227,38 @@ class _TodoPageState extends State { ), ); }, - )), + ), + ), floatingActionButton: FloatingActionButton( backgroundColor: const Color(0xff33b27b), - onPressed: () { - displayTextInputDialog(context, () async { - await CrudSupabase.addTodo( - task: _taskEditingController.text, - due: _dueEditingController.text, - ); - Navigator.pop(context); - setState(() { - loading = true; - }); - - List value = await CrudSupabase.getTodo(); - todos.clear(); - setState(() {}); - for (var element in value) { - todos.add(element); - } - setState(() { - loading = false; - }); - }, _taskEditingController, _dueEditingController); - }, child: const Icon(Icons.add), + onPressed: () { + displayTextInputDialog( + context: context, + taskCtrl: _taskEditingController, + dueCtrl: _dueEditingController, + onTab: () async { + await CrudSupabase.addTodo( + task: _taskEditingController.text, + due: _dueEditingController.text, + ); + Navigator.pop(context); + setState(() { + loading = true; + }); + + List value = await CrudSupabase.getTodo(); + todos.clear(); + setState(() {}); + for (var element in value) { + todos.add(element); + } + setState(() { + loading = false; + }); + }, + ); + }, ), ); } diff --git a/examples/todo-list/flutter-todo-list/pubspec.lock b/examples/todo-list/flutter-todo-list/pubspec.lock index a899da6fff7..4afdab6011f 100644 --- a/examples/todo-list/flutter-todo-list/pubspec.lock +++ b/examples/todo-list/flutter-todo-list/pubspec.lock @@ -42,7 +42,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0" + version: "1.16.0" crypto: dependency: transitive description: @@ -56,7 +56,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" ffi: dependency: transitive description: @@ -76,13 +76,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.4" flutter_svg: dependency: "direct main" description: @@ -148,7 +141,7 @@ packages: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.3" + version: "0.6.4" jwt_decode: dependency: transitive description: @@ -157,12 +150,12 @@ packages: source: hosted version: "0.3.1" lints: - dependency: transitive + dependency: "direct dev" description: name: lints url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.0.0" matcher: dependency: transitive description: @@ -176,7 +169,7 @@ packages: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "0.1.4" meta: dependency: transitive description: @@ -197,7 +190,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" path_drawing: dependency: transitive description: @@ -314,7 +307,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" stack_trace: dependency: transitive description: @@ -370,7 +363,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.8" + version: "0.4.9" typed_data: dependency: transitive description: @@ -468,7 +461,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.1.2" web_socket_channel: dependency: transitive description: @@ -498,5 +491,5 @@ packages: source: hosted version: "5.3.1" sdks: - dart: ">=2.16.1 <3.0.0" + dart: ">=2.17.0-206.0.dev <3.0.0" flutter: ">=2.10.0" diff --git a/examples/todo-list/flutter-todo-list/pubspec.yaml b/examples/todo-list/flutter-todo-list/pubspec.yaml index 4fbe0d09274..3d4df48cc8d 100644 --- a/examples/todo-list/flutter-todo-list/pubspec.yaml +++ b/examples/todo-list/flutter-todo-list/pubspec.yaml @@ -44,7 +44,7 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^1.0.0 + lints: ^2.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec From d9558043821c9dac5dedcde2c7e8d86b10da7fa4 Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton <70828596+Isaiah-Hamilton@users.noreply.github.com> Date: Wed, 8 Jun 2022 20:02:34 -0400 Subject: [PATCH 11/57] chore: update company page --- apps/www/data/Community.tsx | 6 ++--- apps/www/data/Press.ts | 12 +++++++++ apps/www/pages/company.tsx | 49 ++++++++++++++++++++----------------- 3 files changed, 42 insertions(+), 25 deletions(-) diff --git a/apps/www/data/Community.tsx b/apps/www/data/Community.tsx index 618092900c6..1da7c6df686 100644 --- a/apps/www/data/Community.tsx +++ b/apps/www/data/Community.tsx @@ -30,7 +30,7 @@ const data: CommunityItem[] = [ }, { title: 'GitHub', - stat: '32,000+', + stat: '34,000+', statLabel: 'GitHub stars', img: 'github.png', invertImgDarkMode: true, @@ -42,7 +42,7 @@ const data: CommunityItem[] = [ }, { title: 'Twitter', - stat: '30,000+', + stat: '32,000+', statLabel: 'Followers', img: 'twitter.png', detail: () => ( @@ -54,7 +54,7 @@ const data: CommunityItem[] = [ { title: 'Discord', stat: '6,000+', - statLabel: 'SuperTroopers', + statLabel: 'SupaTroopers', img: 'discord.png', detail: () => (
diff --git a/apps/www/data/Press.ts b/apps/www/data/Press.ts index 43f1e2aeb27..4d50795d6a6 100644 --- a/apps/www/data/Press.ts +++ b/apps/www/data/Press.ts @@ -48,6 +48,18 @@ const data: PressItem[] = [ title: 'PodRocket', publisher: 'LogRocket', }, + { + type: 'podcast', + href: 'https://changelog.com/podcast/476', + title: 'Changelog', + publisher: 'Changelog', + }, + { + type: 'podcast', + href: 'https://www.se-radio.net/2022/05/episode-511-ant-wilson-on-supabase-postgres-as-a-service/', + title: 'SE Radio', + publisher: 'SE Radio', + }, ] export default data diff --git a/apps/www/pages/company.tsx b/apps/www/pages/company.tsx index 7563952bb65..ed175340bb4 100644 --- a/apps/www/pages/company.tsx +++ b/apps/www/pages/company.tsx @@ -15,6 +15,7 @@ import InvestorData from 'data/Investors' import TeamData from 'data/Team' import Image from 'next/image' +import Link from 'next/link' import { Button, @@ -312,31 +313,35 @@ const Press = () => {
{PressData.filter((x) => x.type == 'podcast').map((x) => ( - - - -
-

{x.type.toUpperCase()}

-

{x.title}

-
-
-
-
+ + + + +
+

{x.type.toUpperCase()}

+

{x.title}

+
+
+
+
+ ))}
From b61610a39a75fa8eb3b90ea58728089f76221fb6 Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton <70828596+Isaiah-Hamilton@users.noreply.github.com> Date: Wed, 8 Jun 2022 20:08:08 -0400 Subject: [PATCH 12/57] run prettier --- apps/www/pages/company.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/www/pages/company.tsx b/apps/www/pages/company.tsx index ed175340bb4..15a8e89b6ce 100644 --- a/apps/www/pages/company.tsx +++ b/apps/www/pages/company.tsx @@ -313,7 +313,7 @@ const Press = () => {
- {PressData.filter((x) => x.type == 'article').map((x) => + {PressData.filter((x) => x.type == 'article').map((x) => ( @@ -326,12 +326,12 @@ const Press = () => { - )} + ))}
{PressData.filter((x) => x.type == 'podcast').map((x) => ( - +
From f894569d24af497115fc074c93312b6a18e0edbb Mon Sep 17 00:00:00 2001 From: nattwasm <31426677+nattwasm@users.noreply.github.com> Date: Sun, 12 Jun 2022 02:17:46 -0700 Subject: [PATCH 13/57] fix: table row word break --- studio/styles/ui.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/styles/ui.scss b/studio/styles/ui.scss index 5db599ca008..6875da66138 100644 --- a/studio/styles/ui.scss +++ b/studio/styles/ui.scss @@ -142,7 +142,7 @@ } .table-container tbody td { - @apply border-b p-3 px-4 text-sm text-gray-1100 border-table-border-light dark:border-table-border-dark; + @apply border-b p-3 px-4 text-sm text-gray-1100 border-table-border-light break-words dark:border-table-border-dark; } .table-container tbody td:first-child { From 5205964976958cc3055a6b0ef67c0f12badfc023 Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton <70828596+Isaiah-Hamilton@users.noreply.github.com> Date: Tue, 14 Jun 2022 00:12:03 -0400 Subject: [PATCH 14/57] chore: update company page --- apps/www/data/Press.ts | 74 +++++++++++++++++++++++--------------- apps/www/pages/company.tsx | 12 ++++--- apps/www/styles/index.css | 7 ++++ 3 files changed, 61 insertions(+), 32 deletions(-) diff --git a/apps/www/data/Press.ts b/apps/www/data/Press.ts index 4d50795d6a6..8f68720299b 100644 --- a/apps/www/data/Press.ts +++ b/apps/www/data/Press.ts @@ -14,51 +14,69 @@ const data: PressItem[] = [ }, { type: 'article', - href: 'https://techcrunch.com/2021/09/09/supabase-raises-30m-for-its-open-source-insta-backend', + href: 'https://www.prnewswire.com/news-releases/supabase-announces-80-million-series-b-led-by-felicis-301544453.html', + title: 'Supabase Announces $80 Million Series B Led by Felicis', + publisher: 'PR Newswire', + }, + { + type: 'article', + href: 'https://medium.com/lightspeedindia/our-investment-in-supabase-2f443ff55aad', + title: 'Our investment in Supabase', + publisher: 'Medium', + }, + { + type: 'article', + href: 'https://techcrunch.com/2021/09/09/supabase-raises-30m-for-its-open-source-insta-backend/', title: 'Open source backend-as-a-service startup Supabase raises $30M', publisher: 'TechCrunch', }, { type: 'article', - href: 'https://techcrunch.com/2020/12/15/supabase-raises-6m-for-its-open-source-firebase-alternative', + href: 'https://techcrunch.com/2020/12/15/supabase-raises-6m-for-its-open-source-firebase-alternative/', title: 'Supabase raises $6M for its open-source Firebase alternative', publisher: 'TechCrunch', }, { type: 'podcast', - href: 'https://softwareengineeringdaily.com/2020/10/15/supabase-open-source-firebase-with-paul-copplestone', - title: 'Software Engineering Daily', - publisher: 'Software Engineering Daily', - }, - { - type: 'podcast', - href: 'https://fsjam.org/episodes/episode-33-supabase-with-paul-copplestone', - title: 'FS Jam', - publisher: 'FS Jam', - }, - { - type: 'podcast', - href: 'https://www.heavybit.com/library/podcasts/jamstack-radio/ep-71-open-source-firebase-alternative-with-paul-copplestone-of-supabase', - title: 'Jamstack Radio', - publisher: 'Heavybit', - }, - { - type: 'podcast', - href: 'https://podrocket.logrocket.com/9', - title: 'PodRocket', - publisher: 'LogRocket', + href: 'https://podcasts.apple.com/us/podcast/73-elixir-at-supabase-with-paul-copplestone/id1516100616?i=1000542087899', + title: 'Elixir at Supabase with Paul Copplestone', + publisher: 'Thinking Elixir Podcast', }, { type: 'podcast', href: 'https://changelog.com/podcast/476', - title: 'Changelog', - publisher: 'Changelog', + title: 'Supabase is all in on Postgres', + publisher: 'changelog', }, { type: 'podcast', - href: 'https://www.se-radio.net/2022/05/episode-511-ant-wilson-on-supabase-postgres-as-a-service/', - title: 'SE Radio', - publisher: 'SE Radio', + href: 'https://podcasts.apple.com/us/podcast/78-logflare-with-chase-granberry/id1516100616?i=1000545628124', + title: 'Logflare with Chase Granberry', + publisher: 'Thinking Elixir Podcast', + }, + { + type: 'podcast', + href: 'https://stackoverflow.blog/2022/05/17/open-source-is-winning-over-developers-and-investors-ep-442/', + title: 'Open-source is winning over developers and investors', + publisher: 'Stackoverflow', + }, + { + type: 'podcast', + href: 'https://fsjam.org/episodes/episode-33-supabase-with-paul-copplestone', + title: 'Supabase with Paul Copplestone', + publisher: 'FS Jam', + }, + { + type: 'podcast', + href: 'https://www.heavybit.com/library/podcasts/jamstack-radio/ep-71-open-source-firebase-alternative-with-paul-copplestone-of-supabase/', + title: 'Open Source Firebase Alternative with Paul Copplestone of Supabase', + publisher: 'Jamstack', + }, + { + type: 'podcast', + href: 'https://podrocket.logrocket.com/9', + title: 'Supabase with Paul Copplestone', + publisher: 'Podrocket', }, ] diff --git a/apps/www/pages/company.tsx b/apps/www/pages/company.tsx index 15a8e89b6ce..ff673e8de4e 100644 --- a/apps/www/pages/company.tsx +++ b/apps/www/pages/company.tsx @@ -316,11 +316,13 @@ const Press = () => { {PressData.filter((x) => x.type == 'article').map((x) => ( - +

{x.type.toUpperCase()}

-

{x.title}

+

+ {x.title} +

@@ -332,11 +334,13 @@ const Press = () => { {PressData.filter((x) => x.type == 'podcast').map((x) => (
- +

{x.type.toUpperCase()}

-

{x.title}

+

+ {x.title} +

diff --git a/apps/www/styles/index.css b/apps/www/styles/index.css index d770487d130..5f38de88c86 100644 --- a/apps/www/styles/index.css +++ b/apps/www/styles/index.css @@ -223,3 +223,10 @@ table { .sbui-typography a { @apply break-words; } + +.line-clamp { + display: -webkit-box; + margin: 0 auto; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} From 938ab266cfb95f8b6c105090f146afc4acb7a000 Mon Sep 17 00:00:00 2001 From: Copple <10214025+kiwicopple@users.noreply.github.com> Date: Tue, 14 Jun 2022 10:15:02 -0700 Subject: [PATCH 15/57] Blind copy of all the Auth settings --- web/.gitignore | 5 +- web/docs/reference/tools/auth.mdx | 996 ++++++++++++++++++++++++++++++ 2 files changed, 1000 insertions(+), 1 deletion(-) create mode 100644 web/docs/reference/tools/auth.mdx diff --git a/web/.gitignore b/web/.gitignore index daf4804dfa6..bd4efd55fd9 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -5,4 +5,7 @@ docs/client docs/gotrue/client docs/postgrest/client docs/realtime/client -docs/reference \ No newline at end of file + +docs/reference/**/* +!docs/reference/tools/ +!docs/reference/tools/**/* \ No newline at end of file diff --git a/web/docs/reference/tools/auth.mdx b/web/docs/reference/tools/auth.mdx new file mode 100644 index 00000000000..4f439b1013f --- /dev/null +++ b/web/docs/reference/tools/auth.mdx @@ -0,0 +1,996 @@ +--- +id: about +title: Auth Server +description: An SWT based API for managing users and issuing SWT tokens +--- + +GoTrue is a small open-source API written in golang, that can act as a self-standing +API service for handling user registration and authentication for JAM projects. + +It's based on OAuth2 and JWT and will handle user signup, authentication and custom +user data. + +## Quick Start + +Create a `.env` file to store your own custom env vars. See [`example.env`](example.env) + +1. Start the local postgres database in a postgres container: `./hack/postgresd.sh` +2. Build the gotrue binary: `make build` . You should see an output like this: + +``` +go build -ldflags "-X github.com/supabase/gotrue/cmd.Version=`git rev-parse HEAD`" +GOOS=linux GOARCH=arm64 go build -ldflags "-X github.com/supabase/gotrue/cmd.Version=`git rev-parse HEAD`" -o gotrue-arm64 +``` + +3. Execute the gotrue binary: `./gotrue` + +### If you have docker installed... + +Create a `.env.docker` file to store your own custom env vars. See [`example.docker.env`](example.docker.env) + +1. `make build` +2. `make dev` +3. `docker ps` should show 2 docker containers (`gotrue_postgresql` and `gotrue_gotrue`) +4. That's it! Visit the [health checkendpoint](http://localhost:9999/health) to confirm that gotrue is running. + +## Configuration + +You may configure GoTrue using either a configuration file named `.env`, +environment variables, or a combination of both. Environment variables are prefixed with `GOTRUE_`, and will always have precedence over values provided via file. + +### Top-Level + +```properties +GOTRUE_SITE_URL=https://example.netlify.com/ +``` + +`SITE_URL` - `string` **required** + +The base URL your site is located at. Currently used in combination with other settings to construct URLs used in emails. Any URI that shares a host with `SITE_URL` is a permitted value for `redirect_to` params (see `/authorize` etc.). + +`URI_ALLOW_LIST` - `string` + +A comma separated list of URIs (e.g. `"https://foo.example.com,https://*.foo.example.com,https://bar.example.com"`) which are permitted as valid `redirect_to` destinations. Defaults to []. Supports wildcard matching through globbing. e.g. `https://*.foo.example.com` will allow `https://a.foo.example.com` and `https://b.foo.example.com` to be accepted. Globbing is also supported on subdomains. e.g. `https://foo.example.com/*` will allow `https://foo.example.com/page1` and `https://foo.example.com/page2` to be accepted. + +For more common glob patterns, check out the [following link](https://pkg.go.dev/github.com/gobwas/glob#Compile). + +`OPERATOR_TOKEN` - `string` _Multi-instance mode only_ + +The shared secret with an operator (usually Netlify) for this microservice. Used to verify requests have been proxied through the operator and +the payload values can be trusted. + +`DISABLE_SIGNUP` - `bool` + +When signup is disabled the only way to create new users is through invites. Defaults to `false`, all signups enabled. + +`GOTRUE_EXTERNAL_EMAIL_ENABLED` - `bool` + +Use this to disable email signups (users can still use external oauth providers to sign up / sign in) + +`GOTRUE_EXTERNAL_PHONE_ENABLED` - `bool` + +Use this to disable phone signups (users can still use external oauth providers to sign up / sign in) + +`GOTRUE_RATE_LIMIT_HEADER` - `string` + +Header on which to rate limit the `/token` endpoint. + +`GOTRUE_RATE_LIMIT_EMAIL_SENT` - `string` + +Rate limit the number of emails sent per hr on the following endpoints: `/signup`, `/invite`, `/magiclink`, `/recover`, `/otp`, & `/user`. + +`GOTRUE_PASSWORD_MIN_LENGTH` - `int` + +Minimum password length, defaults to 6. + +`GOTRUE_SECURITY_REFRESH_TOKEN_ROTATION_ENABLED` - `bool` + +If refresh token rotation is enabled, gotrue will automatically detect malicious attempts to reuse a revoked refresh token. When a malicious attempt is detected, gotrue immediately revokes all tokens that descended from the offending token. + +`GOTRUE_SECURITY_REFRESH_TOKEN_REUSE_INTERVAL` - `string` + +This setting is only applicable if `GOTRUE_SECURITY_REFRESH_TOKEN_ROTATION_ENABLED` is enabled. The reuse interval for a refresh token allows for exchanging the refresh token multiple times during the interval to support concurrency or offline issues. During the reuse interval, gotrue will not consider using a revoked token as a malicious attempt and will simply return the child refresh token. + +Only the previous revoked token can be reused. Using an old refresh token way before the current valid refresh token will trigger the reuse detection. + +### API + +```properties +GOTRUE_API_HOST=localhost +PORT=9999 +``` + +`API_HOST` - `string` + +Hostname to listen on. + +`PORT` (no prefix) / `API_PORT` - `number` + +Port number to listen on. Defaults to `8081`. + +`API_ENDPOINT` - `string` _Multi-instance mode only_ + +Controls what endpoint Netlify can access this API on. + +`REQUEST_ID_HEADER` - `string` + +If you wish to inherit a request ID from the incoming request, specify the name in this value. + +### Database + +```properties +GOTRUE_DB_DRIVER=mysql +DATABASE_URL=root@localhost/gotrue +``` + +`DB_DRIVER` - `string` **required** + +Chooses what dialect of database you want. Must be `mysql`. + +`DATABASE_URL` (no prefix) / `DB_DATABASE_URL` - `string` **required** + +Connection string for the database. + +`GOTRUE_DB_MAX_POOL_SIZE` - `int` + +Sets the maximum number of open connections to the database. Defaults to 0 which is equivalent to an "unlimited" number of connections. + +`DB_NAMESPACE` - `string` + +Adds a prefix to all table names. + +**Migrations Note** + +Migrations are not applied automatically, so you will need to run them after +you've built gotrue. + +- If built locally: `./gotrue migrate` +- Using Docker: `docker run --rm gotrue gotrue migrate` + +### Logging + +```properties +LOG_LEVEL=debug # available without GOTRUE prefix (exception) +GOTRUE_LOG_FILE=/var/log/go/gotrue.log +``` + +`LOG_LEVEL` - `string` + +Controls what log levels are output. Choose from `panic`, `fatal`, `error`, `warn`, `info`, or `debug`. Defaults to `info`. + +`LOG_FILE` - `string` + +If you wish logs to be written to a file, set `log_file` to a valid file path. + +### Opentracing + +Currently, only the Datadog tracer is supported. + +```properties +GOTRUE_TRACING_ENABLED=true +GOTRUE_TRACING_HOST=127.0.0.1 +GOTRUE_TRACING_PORT=8126 +GOTRUE_TRACING_TAGS="tag1:value1,tag2:value2" +GOTRUE_SERVICE_NAME="gotrue" +``` + +`TRACING_ENABLED` - `bool` + +Whether tracing is enabled or not. Defaults to `false`. + +`TRACING_HOST` - `bool` + +The tracing destination. + +`TRACING_PORT` - `bool` + +The port for the tracing host. + +`TRACING_TAGS` - `string` + +A comma separated list of key:value pairs. These key value pairs will be added as tags to all opentracing spans. + +`SERVICE_NAME` - `string` + +The name to use for the service. + +### JSON Web Tokens (JWT) + +```properties +GOTRUE_JWT_SECRET=supersecretvalue +GOTRUE_JWT_EXP=3600 +GOTRUE_JWT_AUD=netlify +``` + +`JWT_SECRET` - `string` **required** + +The secret used to sign JWT tokens with. + +`JWT_EXP` - `number` + +How long tokens are valid for, in seconds. Defaults to 3600 (1 hour). + +`JWT_AUD` - `string` + +The default JWT audience. Use audiences to group users. + +`JWT_ADMIN_GROUP_NAME` - `string` + +The name of the admin group (if enabled). Defaults to `admin`. + +`JWT_DEFAULT_GROUP_NAME` - `string` + +The default group to assign all new users to. + +### External Authentication Providers + +We support `apple`, `azure`, `bitbucket`, `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin`, `notion`, `spotify`, `slack`, `twitch`, `twitter` and `workos` for external authentication. + +Use the names as the keys underneath `external` to configure each separately. + +```properties +GOTRUE_EXTERNAL_GITHUB_ENABLED=true +GOTRUE_EXTERNAL_GITHUB_CLIENT_ID=myappclientid +GOTRUE_EXTERNAL_GITHUB_SECRET=clientsecretvaluessssh +GOTRUE_EXTERNAL_GITHUB_REDIRECT_URI=http://localhost:3000/callback +``` + +No external providers are required, but you must provide the required values if you choose to enable any. + +`EXTERNAL_X_ENABLED` - `bool` + +Whether this external provider is enabled or not + +`EXTERNAL_X_CLIENT_ID` - `string` **required** + +The OAuth2 Client ID registered with the external provider. + +`EXTERNAL_X_SECRET` - `string` **required** + +The OAuth2 Client Secret provided by the external provider when you registered. + +`EXTERNAL_X_REDIRECT_URI` - `string` **required** + +The URI a OAuth2 provider will redirect to with the `code` and `state` values. + +`EXTERNAL_X_URL` - `string` + +The base URL used for constructing the URLs to request authorization and access tokens. Used by `gitlab` and `keycloak`. For `gitlab` it defaults to `https://gitlab.com`. For `keycloak` you need to set this to your instance, for example: `https://keycloak.example.com/auth/realms/myrealm` + +#### Apple OAuth + +To try out external authentication with Apple locally, you will need to do the following: + +1. Remap localhost to \ in your `/etc/hosts` config. +2. Configure gotrue to serve HTTPS traffic over localhost by replacing `ListenAndServe` in [api.go](api/api.go) with: + ``` + func (a *API) ListenAndServe(hostAndPort string) { + log := logrus.WithField("component", "api") + path, err := os.Getwd() + if err != nil { + log.Println(err) + } + server := &http.Server{ + Addr: hostAndPort, + Handler: a.handler, + } + done := make(chan struct{}) + defer close(done) + go func() { + waitForTermination(log, done) + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + server.Shutdown(ctx) + }() + if err := server.ListenAndServeTLS("PATH_TO_CRT_FILE", "PATH_TO_KEY_FILE"); err != http.ErrServerClosed { + log.WithError(err).Fatal("http server listen failed") + } + } + ``` +3. Generate the crt and key file. See [here](https://www.freecodecamp.org/news/how-to-get-https-working-on-your-local-development-environment-in-5-minutes-7af615770eec/) for more information. +4. Generate the `GOTRUE_EXTERNAL_APPLE_SECRET` by following this [post](https://medium.com/identity-beyond-borders/how-to-configure-sign-in-with-apple-77c61e336003)! + +### E-Mail + +Sending email is not required, but highly recommended for password recovery. +If enabled, you must provide the required values below. + +```properties +GOTRUE_SMTP_HOST=smtp.mandrillapp.com +GOTRUE_SMTP_PORT=587 +GOTRUE_SMTP_USER=smtp-delivery@example.com +GOTRUE_SMTP_PASS=correcthorsebatterystaple +GOTRUE_SMTP_ADMIN_EMAIL=support@example.com +GOTRUE_MAILER_SUBJECTS_CONFIRMATION="Please confirm" +``` + +`SMTP_ADMIN_EMAIL` - `string` **required** + +The `From` email address for all emails sent. + +`SMTP_HOST` - `string` **required** + +The mail server hostname to send emails through. + +`SMTP_PORT` - `number` **required** + +The port number to connect to the mail server on. + +`SMTP_USER` - `string` + +If the mail server requires authentication, the username to use. + +`SMTP_PASS` - `string` + +If the mail server requires authentication, the password to use. + +`SMTP_MAX_FREQUENCY` - `number` + +Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email. The value is the number of seconds. Defaults to 900 (15 minutes). + +`SMTP_SENDER_NAME` - `string` + +Sets the name of the sender. Defaults to the `SMTP_ADMIN_EMAIL` if not used. + +`MAILER_AUTOCONFIRM` - `bool` + +If you do not require email confirmation, you may set this to `true`. Defaults to `false`. + +`MAILER_OTP_EXP` - `number` + +Controls the duration an email link or otp is valid for. + +`MAILER_URLPATHS_INVITE` - `string` + +URL path to use in the user invite email. Defaults to `/`. + +`MAILER_URLPATHS_CONFIRMATION` - `string` + +URL path to use in the signup confirmation email. Defaults to `/`. + +`MAILER_URLPATHS_RECOVERY` - `string` + +URL path to use in the password reset email. Defaults to `/`. + +`MAILER_URLPATHS_EMAIL_CHANGE` - `string` + +URL path to use in the email change confirmation email. Defaults to `/`. + +`MAILER_SUBJECTS_INVITE` - `string` + +Email subject to use for user invite. Defaults to `You have been invited`. + +`MAILER_SUBJECTS_CONFIRMATION` - `string` + +Email subject to use for signup confirmation. Defaults to `Confirm Your Signup`. + +`MAILER_SUBJECTS_RECOVERY` - `string` + +Email subject to use for password reset. Defaults to `Reset Your Password`. + +`MAILER_SUBJECTS_MAGIC_LINK` - `string` + +Email subject to use for magic link email. Defaults to `Your Magic Link`. + +`MAILER_SUBJECTS_EMAIL_CHANGE` - `string` + +Email subject to use for email change confirmation. Defaults to `Confirm Email Change`. + +`MAILER_TEMPLATES_INVITE` - `string` + +URL path to an email template to use when inviting a user. +`SiteURL`, `Email`, and `ConfirmationURL` variables are available. + +Default Content (if template is unavailable): + +```html +

You have been invited

+ +

+ You have been invited to create a user on {{ .SiteURL }}. Follow this link to accept the invite: +

+

Accept the invite

+``` + +`MAILER_TEMPLATES_CONFIRMATION` - `string` + +URL path to an email template to use when confirming a signup. +`SiteURL`, `Email`, and `ConfirmationURL` variables are available. + +Default Content (if template is unavailable): + +```html +

Confirm your signup

+ +

Follow this link to confirm your user:

+

Confirm your mail

+``` + +`MAILER_TEMPLATES_RECOVERY` - `string` + +URL path to an email template to use when resetting a password. +`SiteURL`, `Email`, and `ConfirmationURL` variables are available. + +Default Content (if template is unavailable): + +```html +

Reset Password

+ +

Follow this link to reset the password for your user:

+

Reset Password

+``` + +`MAILER_TEMPLATES_MAGIC_LINK` - `string` + +URL path to an email template to use when sending magic link. +`SiteURL`, `Email`, and `ConfirmationURL` variables are available. + +Default Content (if template is unavailable): + +```html +

Magic Link

+ +

Follow this link to login:

+

Log In

+``` + +`MAILER_TEMPLATES_EMAIL_CHANGE` - `string` + +URL path to an email template to use when confirming the change of an email address. +`SiteURL`, `Email`, `NewEmail`, and `ConfirmationURL` variables are available. + +Default Content (if template is unavailable): + +```html +

Confirm Change of Email

+ +

Follow this link to confirm the update of your email from {{ .Email }} to {{ .NewEmail }}:

+

Change Email

+``` + +`WEBHOOK_URL` - `string` + +Url of the webhook receiver endpoint. This will be called when events like `validate`, `signup` or `login` occur. + +`WEBHOOK_SECRET` - `string` + +Shared secret to authorize webhook requests. This secret signs the [JSON Web Signature](https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41) of the request. You _should_ use this to verify the integrity of the request. Otherwise others can feed your webhook receiver with fake data. + +`WEBHOOK_RETRIES` - `number` + +How often GoTrue should try a failed hook. + +`WEBHOOK_TIMEOUT_SEC` - `number` + +Time between retries (in seconds). + +`WEBHOOK_EVENTS` - `list` + +Which events should trigger a webhook. You can provide a comma separated list. +For example to listen to all events, provide the values `validate,signup,login`. + +### Phone Auth + +`SMS_AUTOCONFIRM` - `bool` + +If you do not require phone confirmation, you may set this to `true`. Defaults to `false`. + +`SMS_MAX_FREQUENCY` - `number` + +Controls the minimum amount of time that must pass before sending another sms otp. The value is the number of seconds. Defaults to 60 (1 minute)). + +`SMS_OTP_EXP` - `number` + +Controls the duration an sms otp is valid for. + +`SMS_OTP_LENGTH` - `number` + +Controls the number of digits of the sms otp sent. + +`SMS_PROVIDER` - `string` + +Available options are: `twilio`, `messagebird`, `textlocal`, and `vonage` + +Then you can use your [twilio credentials](https://www.twilio.com/docs/usage/requests-to-twilio#credentials): + +- `SMS_TWILIO_ACCOUNT_SID` +- `SMS_TWILIO_AUTH_TOKEN` +- `SMS_TWILIO_MESSAGE_SERVICE_SID` - can be set to your twilio sender mobile number + +Or Messagebird credentials, which can be obtained in the [Dashboard](https://dashboard.messagebird.com/en/developers/access): + +- `SMS_MESSAGEBIRD_ACCESS_KEY` - your Messagebird access key +- `SMS_MESSAGEBIRD_ORIGINATOR` - SMS sender (your Messagebird phone number with + or company name) + +### CAPTCHA + +- If enabled, CAPTCHA will check the request body for the `hcaptcha_token` field and make a verification request to the CAPTCHA provider. + +`SECURITY_CAPTCHA_ENABLED` - `string` + +Whether captcha middleware is enabled + +`SECURITY_CAPTCHA_PROVIDER` - `string` + +for now the only option supported is: `hcaptcha` + +- `SECURITY_CAPTCHA_SECRET` - `string` +- `SECURITY_CAPTCHA_TIMEOUT` - `string` + +Retrieve from hcaptcha account + +### Reauthentication + +`SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION` - `bool` + +Enforce reauthentication on password update. + +## Endpoints + +GoTrue exposes the following endpoints: + +### **GET /settings** + +Returns the publicly available settings for this gotrue instance. + +```json +{ + "external": { + "apple": true, + "azure": true, + "bitbucket": true, + "discord": true, + "facebook": true, + "github": true, + "gitlab": true, + "google": true, + "keycloak": true, + "linkedin": true, + "notion": true, + "slack": true, + "spotify": true, + "twitch": true, + "twitter": true, + "workos": true + }, + "disable_signup": false, + "autoconfirm": false +} +``` + +### **POST, PUT /admin/users/** + +Creates (POST) or Updates (PUT) the user based on the `user_id` specified. The `ban_duration` field accepts the following time units: "ns", "us", "ms", "s", "m", "h". See [`time.ParseDuration`](https://pkg.go.dev/time#ParseDuration) for more details on the format used. + +```js +headers: +{ + "Authorization": "Bearer eyJhbGciOiJI...M3A90LCkxxtX9oNP9KZO" // admin role required +} + +body: +{ + "role": "test-user", + "email": "email@example.com", + "phone": "12345678", + "password": "secret", // only if type = signup + "email_confirm": true, + "phone_confirm": true, + "user_metadata": {}, + "app_metadata": {}, + "ban_duration": "24h" or "none" // to unban a user +} +``` + +### **POST /admin/generate_link** + +Returns the corresponding email action link based on the type specified. + +```js +headers: +{ + "Authorization": "Bearer eyJhbGciOiJI...M3A90LCkxxtX9oNP9KZO" // admin role required +} + +body: +{ + "type": "signup" or "magiclink" or "recovery" or "invite", + "email": "email@example.com", + "password": "secret", // only if type = signup + "data": { + ... + }, // only if type = signup + "redirect_to": "https://supabase.io" // Redirect URL to send the user to after an email action. Defaults to SITE_URL. + +} +``` + +Returns + +```js +{ + "action_link": "http://localhost:9999/verify?token=TOKEN&type=TYPE&redirect_to=REDIRECT_URL", + ... +} +``` + +### **POST /signup** + +Register a new user with an email and password. + +```js +{ + "email": "email@example.com", + "password": "secret" +} +``` + +returns: + +```json +{ + "id": "11111111-2222-3333-4444-5555555555555", + "email": "email@example.com", + "confirmation_sent_at": "2016-05-15T20:49:40.882805774-07:00", + "created_at": "2016-05-15T19:53:12.368652374-07:00", + "updated_at": "2016-05-15T19:53:12.368652374-07:00" +} + +// if sign up is a duplicate then faux data will be returned +// as to not leak information about whether a given email +// has an account with your service or not +``` + +Register a new user with a phone number and password. + +```js +{ + "phone": "12345678", // follows the E.164 format + "password": "secret" +} +``` + +Returns: + +```json +{ + "id": "11111111-2222-3333-4444-5555555555555", // if duplicate sign up, this ID will be faux + "phone": "12345678", + "confirmation_sent_at": "2016-05-15T20:49:40.882805774-07:00", + "created_at": "2016-05-15T19:53:12.368652374-07:00", + "updated_at": "2016-05-15T19:53:12.368652374-07:00" +} +``` + +if AUTOCONFIRM is enabled and the sign up is a duplicate, then the endpoint will return: + +``` +{ + "code":400, + "msg":"User already registered" +} +``` + +### **POST /invite** + +Invites a new user with an email. +This endpoint requires the `service_role` or `supabase_admin` JWT set as an Auth Bearer header: + +e.g. + +```json +headers: { + "Authorization" : "Bearer eyJhbGciOiJI...M3A90LCkxxtX9oNP9KZO" +} +``` + +```json +{ + "email": "email@example.com" +} +``` + +Returns: + +```json +{ + "id": "11111111-2222-3333-4444-5555555555555", + "email": "email@example.com", + "confirmation_sent_at": "2016-05-15T20:49:40.882805774-07:00", + "created_at": "2016-05-15T19:53:12.368652374-07:00", + "updated_at": "2016-05-15T19:53:12.368652374-07:00", + "invited_at": "2016-05-15T19:53:12.368652374-07:00" +} +``` + +### **POST /verify** + +Verify a registration or a password recovery. Type can be `signup` or `recovery` or `invite` +and the `token` is a token returned from either `/signup` or `/recover`. + +```json +{ + "type": "signup", + "token": "confirmation-code-delivered-in-email" +} +``` + +`password` is required for signup verification if no existing password exists. + +Returns: + +```json +{ + "access_token": "jwt-token-representing-the-user", + "token_type": "bearer", + "expires_in": 3600, + "refresh_token": "a-refresh-token", + "type": "signup | recovery | invite" +} +``` + +Verify a phone signup or sms otp. Type should be set to `sms`. + +```json +{ + "type": "sms", + "token": "confirmation-otp-delivered-in-sms", + "redirect_to": "https://supabase.io", + "phone": "phone-number-sms-otp-was-delivered-to" +} +``` + +Returns: + +```json +{ + "access_token": "jwt-token-representing-the-user", + "token_type": "bearer", + "expires_in": 3600, + "refresh_token": "a-refresh-token" +} +``` + +### **GET /verify** + +Verify a registration or a password recovery. Type can be `signup` or `recovery` or `magiclink` or `invite` +and the `token` is a token returned from either `/signup` or `/recover` or `/magiclink`. + +query params: + +```json +{ + "type": "signup", + "token": "confirmation-code-delivered-in-email", + "redirect_to": "https://supabase.io" +} +``` + +User will be logged in and redirected to: + +```json +SITE_URL/#access_token=jwt-token-representing-the-user&token_type=bearer&expires_in=3600&refresh_token=a-refresh-token&type=invite +``` + +Your app should detect the query params in the fragment and use them to set the session (supabase-js does this automatically) + +You can use the `type` param to redirect the user to a password set form in the case of `invite` or `recovery`, +or show an account confirmed/welcome message in the case of `signup`, or direct them to some additional onboarding flow + +### **POST /otp** + +One-Time-Password. Will deliver a magiclink or sms otp to the user depending on whether the request body contains an "email" or "phone" key. + +If `"create_user": true`, user will not be automatically signed up if the user doesn't exist. + +```js +{ + "phone": "12345678" // follows the E.164 format + "create_user": true +} + +OR + +// exactly the same as /magiclink +{ + "email": "email@example.com" + "create_user": true +} +``` + +Returns: + +``` +{} +``` + +### **POST /magiclink** (recommended to use /otp instead. See above.) + +Magic Link. Will deliver a link (e.g. `/verify?type=magiclink&token=fgtyuf68ddqdaDd`) to the user based on +email address which they can use to redeem an access_token. + +By default Magic Links can only be sent once every 60 seconds + +```json +{ + "email": "email@example.com" +} +``` + +Returns: + +```json +{} +``` + +when clicked the magic link will redirect the user to `#access_token=x&refresh_token=y&expires_in=z&token_type=bearer&type=magiclink` (see `/verify` above) + +### **POST /recover** + +Password recovery. Will deliver a password recovery mail to the user based on +email address. + +By default recovery links can only be sent once every 60 seconds + +```json +{ + "email": "email@example.com" +} +``` + +Returns: + +```json +{} +``` + +### **POST /token** + +This is an OAuth2 endpoint that currently implements +the password and refresh_token grant types + +query params: + +``` +?grant_type=password +``` + +body: + +```json +// Email login +{ + "email": "name@domain.com", + "password": "somepassword" +} + +// Phone login +{ + "phone": "12345678", + "password": "somepassword" +} +``` + +or + +query params: + +``` +grant_type=refresh_token +``` + +body: + +```json +{ + "refresh_token": "a-refresh-token" +} +``` + +Once you have an access token, you can access the methods requiring authentication +by settings the `Authorization: Bearer YOUR_ACCESS_TOKEN_HERE` header. + +Returns: + +```json +{ + "access_token": "jwt-token-representing-the-user", + "token_type": "bearer", + "expires_in": 3600, + "refresh_token": "a-refresh-token" +} +``` + +### **GET /user** + +Get the JSON object for the logged in user (requires authentication) + +Returns: + +```json +{ + "id": "11111111-2222-3333-4444-5555555555555", + "email": "email@example.com", + "confirmation_sent_at": "2016-05-15T20:49:40.882805774-07:00", + "created_at": "2016-05-15T19:53:12.368652374-07:00", + "updated_at": "2016-05-15T19:53:12.368652374-07:00" +} +``` + +### **PUT /user** + +Update a user (Requires authentication). Apart from changing email/password, this +method can be used to set custom user data. Changing the email will result in a magiclink being sent out. + +```json +{ + "email": "new-email@example.com", + "password": "new-password", + "data": { + "key": "value", + "number": 10, + "admin": false + } +} +``` + +Returns: + +```json +{ + "id": "11111111-2222-3333-4444-5555555555555", + "email": "email@example.com", + "email_change_sent_at": "2016-05-15T20:49:40.882805774-07:00", + "created_at": "2016-05-15T19:53:12.368652374-07:00", + "updated_at": "2016-05-15T19:53:12.368652374-07:00" +} +``` + +If `GOTRUE_SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION` is enabled, the user will need to reauthenticate first. + +```json +{ + "password": "new-password", + "nonce": "123456" +} +``` + +### **GET /reauthenticate** + +Sends a nonce to the user's email (preferred) or phone. This endpoint requires the user to be logged in / authenticated first. The user needs to have either an email or phone number for the nonce to be sent successfully. + +```json +headers: { + "Authorization" : "Bearer eyJhbGciOiJI...M3A90LCkxxtX9oNP9KZO" +} +``` + +### **POST /logout** + +Logout a user (Requires authentication). + +This will revoke all refresh tokens for the user. Remember that the JWT tokens +will still be valid for stateless auth until they expires. + +### **GET /authorize** + +Get access_token from external oauth provider + +query params: + +``` +provider=apple | azure | bitbucket | discord | facebook | github | gitlab | google | keycloak | linkedin | notion | slack | spotify | twitch | twitter | workos + +scopes= +``` + +Redirects to provider and then to `/callback` + +For apple specific setup see: https://github.com/supabase/gotrue#apple-oauth + +### **GET /callback** + +External provider should redirect to here + +Redirects to `#access_token=&refresh_token=&provider_token=&expires_in=3600&provider=` +If additional scopes were requested then `provider_token` will be populated, you can use this to fetch additional data from the provider or interact with their services From fddcf3f93cad41d197d78721bcc07565ea2131d3 Mon Sep 17 00:00:00 2001 From: Copple <10214025+kiwicopple@users.noreply.github.com> Date: Tue, 14 Jun 2022 10:42:52 -0700 Subject: [PATCH 16/57] Adds an auth section --- web/docs/reference.mdx | 28 ++++++++++++++++++++++++++++ web/docs/reference/tools/auth.mdx | 2 +- web/sidebars.js | 13 ++++++++++++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/web/docs/reference.mdx b/web/docs/reference.mdx index 6cf5cdba2f8..fb218e6e2ad 100644 --- a/web/docs/reference.mdx +++ b/web/docs/reference.mdx @@ -34,6 +34,34 @@ The Supabase Reference Docs provide technical descriptions of the products and h
+## Tools + +The Supabase Reference Docs provide technical descriptions of the products and how to use them. + +
+
+ {/* Examples - coming soon */} +
+ +
+

Auth

+

Auth reference.

+
+ +
+ {/* CLI */} +
+ +
+

Connection Pool

+

PgBouncer reference docs.

+
+ +
+ {/* */} +
+
+ ## Client Libraries
diff --git a/web/docs/reference/tools/auth.mdx b/web/docs/reference/tools/auth.mdx index 4f439b1013f..ba029706560 100644 --- a/web/docs/reference/tools/auth.mdx +++ b/web/docs/reference/tools/auth.mdx @@ -1,5 +1,5 @@ --- -id: about +id: reference-auth title: Auth Server description: An SWT based API for managing users and issuing SWT tokens --- diff --git a/web/sidebars.js b/web/sidebars.js index e9965c9b5e9..933549a774f 100755 --- a/web/sidebars.js +++ b/web/sidebars.js @@ -36,6 +36,12 @@ module.exports = { collapsed: false, items: supabaseCli.docs, }, + { + type: 'category', + label: 'Tools', + collapsed: false, + items: ['reference/tools/reference-auth'], + }, ], docs: [ { @@ -185,7 +191,12 @@ module.exports = { type: 'category', label: 'Platform', collapsed: false, - items: ['guides/platform/logs', 'guides/platform/metrics', 'going-into-prod', 'guides/platform/performance'], + items: [ + 'guides/platform/logs', + 'guides/platform/metrics', + 'going-into-prod', + 'guides/platform/performance', + ], }, { type: 'category', From 5a9ed92dbe484a2894be6d0174a6271cf14af60b Mon Sep 17 00:00:00 2001 From: nattwasm <31426677+nattwasm@users.noreply.github.com> Date: Tue, 14 Jun 2022 17:21:52 -0700 Subject: [PATCH 17/57] feedback --- studio/styles/ui.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/styles/ui.scss b/studio/styles/ui.scss index 6875da66138..98800ecfafa 100644 --- a/studio/styles/ui.scss +++ b/studio/styles/ui.scss @@ -142,7 +142,7 @@ } .table-container tbody td { - @apply border-b p-3 px-4 text-sm text-gray-1100 border-table-border-light break-words dark:border-table-border-dark; + @apply border-b p-3 px-4 text-sm text-gray-1100 border-table-border-light truncate dark:border-table-border-dark; } .table-container tbody td:first-child { From a5700b98ccc5151da768cc013c1f398fabd47d63 Mon Sep 17 00:00:00 2001 From: nattwasm <31426677+nattwasm@users.noreply.github.com> Date: Tue, 14 Jun 2022 17:29:10 -0700 Subject: [PATCH 18/57] feedback 2 --- studio/styles/ui.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/studio/styles/ui.scss b/studio/styles/ui.scss index 98800ecfafa..edd4643abf4 100644 --- a/studio/styles/ui.scss +++ b/studio/styles/ui.scss @@ -113,6 +113,10 @@ border-spacing: 0; } +.table-container table span { + @apply truncate; +} + .table-container thead th { @apply text-scale-900 font-normal text-sm; @apply bg-table-header-light dark:bg-table-header-dark; @@ -142,7 +146,7 @@ } .table-container tbody td { - @apply border-b p-3 px-4 text-sm text-gray-1100 border-table-border-light truncate dark:border-table-border-dark; + @apply border-b p-3 px-4 text-sm text-gray-1100 border-table-border-light dark:border-table-border-dark; } .table-container tbody td:first-child { From 6e4f3b03f929136a6584ff3f3d20cdc575d594f7 Mon Sep 17 00:00:00 2001 From: Austin Gregersen Date: Wed, 15 Jun 2022 13:23:10 -0700 Subject: [PATCH 19/57] fix prisma guide typos --- web/docs/guides/integrations/prisma.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/docs/guides/integrations/prisma.mdx b/web/docs/guides/integrations/prisma.mdx index 635e3bb9124..09411d99dfe 100644 --- a/web/docs/guides/integrations/prisma.mdx +++ b/web/docs/guides/integrations/prisma.mdx @@ -23,7 +23,7 @@ To make sure that everything works correctly, let’s try the connection string If you already have one, all you need to do is set the `DATABASE_URL` to the connection string (including the password) in your `.env` file, and you’re good to go. -In case you don’t have a Prisma project or this is your first time working with Prisma, you’re going to use with the repo from the [quickstart](https://www.prisma.io/docs/getting-started/quickstart) guide. +In case you don’t have a Prisma project or this is your first time working with Prisma, you’re going to use the repo from the [quickstart](https://www.prisma.io/docs/getting-started/quickstart) guide. ### Cloning the starter project @@ -140,10 +140,10 @@ alter type "Incompatible" rename to compatible; ## Connection pooling with Supabase -If you’re working in a serverless environment (for example Node.js functions hosted on AWS Lambda, Vercel or Netlify Functions), you need to set up [connection pooling](https://www.prisma.io/docs/guides/performance-and-optimization/connection-management#serverless-environments-faas) using a tool like [PgBouncer](https://www.pgbouncer.org/). That’s because every function invocation may result in a [new connection to the database](https://www.prisma.io/docs/guides/performance-and-optimization/connection-management#the-serverless-challenge). Supabase [support connection management using PgBouncer](https://supabase.io/blog/2021/04/02/supabase-pgbouncer#what-is-connection-pooling) and are enabled by default. +If you’re working in a serverless environment (for example Node.js functions hosted on AWS Lambda, Vercel or Netlify Functions), you need to set up [connection pooling](https://www.prisma.io/docs/guides/performance-and-optimization/connection-management#serverless-environments-faas) using a tool like [PgBouncer](https://www.pgbouncer.org/). That’s because every function invocation may result in a [new connection to the database](https://www.prisma.io/docs/guides/performance-and-optimization/connection-management#the-serverless-challenge). Supabase [supports connection management using PgBouncer](https://supabase.io/blog/2021/04/02/supabase-pgbouncer#what-is-connection-pooling) and are enabled by default. Go to the **Database** page from the sidebar in the Supabase dashboard and navigate to **connection pool** settings ![Connection pool settings](/img/guides/integrations/prisma/w0oowg8vq435ob5c3gf0.png) -When running migrations you need to use the non pooled connection URL (like the one we used in **step 4**). However, when deploying your app, you’ll use the pooled connection URL and add the `?pgbouncer=true` flag to the PostgreSQL connection URL. To minimize the number of concurrent connections, setting the `connection_limit` to `1` is also recommended. So The URL might look as follows: +When running migrations you need to use the non pooled connection URL (like the one we used in **step 4**). However, when deploying your app, you’ll use the pooled connection URL and add the `?pgbouncer=true` flag to the PostgreSQL connection URL. To minimize the number of concurrent connections, setting the `connection_limit` to `1` is also recommended. So the URL might look as follows: ```env # prisma/.env From e80887a16ae324a68f4bc706e716892850424f3b Mon Sep 17 00:00:00 2001 From: Jan Marsh Date: Thu, 16 Jun 2022 10:55:15 +0200 Subject: [PATCH 20/57] Update api.mdx Fixing JavaScript realtime api code example --- web/docs/guides/api.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/docs/guides/api.mdx b/web/docs/guides/api.mdx index e9fff486e3b..077eba742ac 100644 --- a/web/docs/guides/api.mdx +++ b/web/docs/guides/api.mdx @@ -358,10 +358,10 @@ const handleInserts = (payload) => { console.log('Change received!', payload) } -// Listen to unserts +// Listen to inserts const { data: todos, error } = await supabase .from('todos') - .on('INSERT', handleUpdates) + .on('INSERT', handleInserts) .subscribe() ``` From c86bc0f51f12880e7222ec663feca668b3926d1e Mon Sep 17 00:00:00 2001 From: Ollie Halliday <22655069+Hallidayo@users.noreply.github.com> Date: Thu, 16 Jun 2022 20:50:13 +0100 Subject: [PATCH 21/57] timestamp casting as time and formatting changes --- .../Settings/Logs/Logs.constants.ts | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/studio/components/interfaces/Settings/Logs/Logs.constants.ts b/studio/components/interfaces/Settings/Logs/Logs.constants.ts index ddd889a38b8..af7cd0d5afb 100644 --- a/studio/components/interfaces/Settings/Logs/Logs.constants.ts +++ b/studio/components/interfaces/Settings/Logs/Logs.constants.ts @@ -21,7 +21,8 @@ export const TEMPLATES: LogTemplate[] = [ description: 'Count of commits made by users on the database', mode: 'custom', searchString: `select - p.user_name, count(*) as count + p.user_name, + count(*) as count from postgres_logs left join unnest(metadata) as m on true left join unnest(m.parsed) as p on true @@ -29,14 +30,16 @@ where regexp_contains(event_message, 'COMMIT') group by p.user_name - `, + `, for: ['database'], }, { label: 'Metadata IP', description: 'List all IP addresses that used the Supabase API', mode: 'custom', - searchString: `select timestamp, h.x_real_ip + searchString: `select + cast(timestamp as datetime) as timestamp, + h.x_real_ip from edge_logs left join unnest(metadata) as m on true left join unnest(m.request) as r on true @@ -50,7 +53,8 @@ where h.x_real_ip is not null description: 'List all ISO 3166-1 alpha-2 country codes that used the Supabase API', mode: 'custom', searchString: `select - cf.country, count(*) as count + cf.country, + count(*) as count from edge_logs left join unnest(metadata) as m on true left join unnest(m.request) as r on true @@ -67,7 +71,7 @@ order by mode: 'custom', description: 'List all Supabase API requests that are slow', searchString: `select - timestamp, + cast(timestamp as datetime) as timestamp, event_message, r.origin_time from edge_logs @@ -85,8 +89,8 @@ limit 100 label: '500 Request Codes', description: 'List all Supabase API requests that responded witha 5XX status code', mode: 'custom', - searchString: `SELECT - timestamp, + searchString: `select + cast(timestamp as datetime) as timestamp, event_message, r.status_code from edge_logs @@ -104,7 +108,7 @@ limit 100 label: 'Top Paths', description: 'List the most requested Supabase API paths', mode: 'custom', - searchString: `SELECT + searchString: `select r.path as path, r.search as params, count(timestamp) as c @@ -124,8 +128,8 @@ limit 100 label: 'REST Requests', description: 'List all PostgREST requests', mode: 'custom', - searchString: `SELECT - timestamp, + searchString: `select + cast(timestamp as datetime) as timestamp, event_message from edge_logs cross join unnest(metadata) as m @@ -142,14 +146,13 @@ limit 100 label: 'Errors', description: 'List all Postgres error messages with ERROR, FATAL, or PANIC severity', mode: 'custom', - searchString: `SELECT - t.timestamp, + searchString: `select + cast(t.timestamp as datetime) as timestamp, p.error_severity, event_message -from - postgres_logs as t - cross join unnest(metadata) as m - cross join unnest(m.parsed) as p +from postgres_logs as t + cross join unnest(metadata) as m + cross join unnest(m.parsed) as p where p.error_severity in ('ERROR', 'FATAL', 'PANIC') order by @@ -165,10 +168,9 @@ limit 100 count(t.timestamp) as count, p.user_name, p.error_severity -from - postgres_logs as t - cross join unnest(metadata) as m - cross join unnest(m.parsed) as p +from postgres_logs as t + cross join unnest(metadata) as m + cross join unnest(m.parsed) as p where p.error_severity in ('ERROR', 'FATAL', 'PANIC') group by From 949458558e7504e5a500c22aee9cda036b0fd62e Mon Sep 17 00:00:00 2001 From: Kang Ming Date: Thu, 16 Jun 2022 12:54:59 -0700 Subject: [PATCH 22/57] update metadata --- web/docs/reference/tools/auth.mdx | 1023 ++++------------------------- web/package-lock.json | 142 +--- web/package.json | 2 +- web/spec/combined.json | 4 +- web/spec/storage.json | 4 +- 5 files changed, 168 insertions(+), 1007 deletions(-) diff --git a/web/docs/reference/tools/auth.mdx b/web/docs/reference/tools/auth.mdx index ba029706560..c1c3ed60198 100644 --- a/web/docs/reference/tools/auth.mdx +++ b/web/docs/reference/tools/auth.mdx @@ -8,219 +8,68 @@ GoTrue is a small open-source API written in golang, that can act as a self-stan API service for handling user registration and authentication for JAM projects. It's based on OAuth2 and JWT and will handle user signup, authentication and custom -user data. +user data. -## Quick Start - -Create a `.env` file to store your own custom env vars. See [`example.env`](example.env) - -1. Start the local postgres database in a postgres container: `./hack/postgresd.sh` -2. Build the gotrue binary: `make build` . You should see an output like this: - -``` -go build -ldflags "-X github.com/supabase/gotrue/cmd.Version=`git rev-parse HEAD`" -GOOS=linux GOARCH=arm64 go build -ldflags "-X github.com/supabase/gotrue/cmd.Version=`git rev-parse HEAD`" -o gotrue-arm64 -``` - -3. Execute the gotrue binary: `./gotrue` - -### If you have docker installed... - -Create a `.env.docker` file to store your own custom env vars. See [`example.docker.env`](example.docker.env) - -1. `make build` -2. `make dev` -3. `docker ps` should show 2 docker containers (`gotrue_postgresql` and `gotrue_gotrue`) -4. That's it! Visit the [health checkendpoint](http://localhost:9999/health) to confirm that gotrue is running. +If you're using gotrue on the Supabase platform, some of these options are not exposed on the dashboard as they are set internally. +If you're self-hosting gotrue on your own infrastructure, you will need to maintain these configuration options in an `.env` file. Most of these configuration options need to be prefixed with `GOTRUE_` if self-hosting. ## Configuration -You may configure GoTrue using either a configuration file named `.env`, -environment variables, or a combination of both. Environment variables are prefixed with `GOTRUE_`, and will always have precedence over values provided via file. +The following list provides an overview of the configuration set in gotrue. -### Top-Level +### Security -```properties -GOTRUE_SITE_URL=https://example.netlify.com/ -``` +| Parameter | Type | Description | +| :-------- | :------- | :------------------------- | +| `SITE_URL` | `string` | **Required**. The base URL your site is located at. Currently used in combination with other settings to construct URLs used in emails. Any URI that shares a host with `SITE_URL` is a permitted value for `redirect_to` params (see `/authorize` etc.). | +| `URI_ALLOW_LIST` | `string` | A comma separated list of URIs (e.g. `"https://foo.example.com,https://*.foo.example.com"`) which are permitted as valid `redirect_to` destinations. Defaults to [ ].

Supports wildcard matching through globbing. (e.g. `https://*.foo.example.com` will allow `https://a.foo.example.com` and `https://b.foo.example.com` to be accepted.)

Globbing is also supported on subdomains. (e.g. `https://foo.example.com/*` will allow `https://foo.example.com/page1` and `https://foo.example.com/page2` to be accepted.)
For more common glob patterns, check out the [following link](https://pkg.go.dev/github.com/gobwas/glob#Compile).| +| `OPERATOR_TOKEN` | `string` | The shared secret with an operator for this microservice. Used to verify requests have been proxied through the operator and the payload values can be trusted. | +| `DISABLE_SIGNUP` | `bool` | When signup is disabled the only way to create new users is through invites. Defaults to `false`, all signups enabled. | +| `EXTERNAL_EMAIL_ENABLED` | `bool` | Use this to disable email signups (users can still use external oauth providers to sign up / sign in) | +| `EXTERNAL_PHONE_ENABLED` | `bool` | Use this to disable phone signups (users can still use external oauth providers to sign up / sign in) | +| `RATE_LIMIT_TOKEN_REFRESH` | `string` | Rate limit the number of requests sent to `/token` | +| `RATE_LIMIT_EMAIL_SENT` | `string` | Rate limit the number of emails sent per hr on the following endpoints: `/signup`, `/invite`, `/magiclink`, `/recover`, `/otp`, & `/user`. | +| `PASSWORD_MIN_LENGTH` | `int` | Minimum password length, defaults to 6. | -`SITE_URL` - `string` **required** +These options control additional security settings available in gotrue. If self-hosting, they need to be prefixed with `GOTRUE_SECURITY_` . -The base URL your site is located at. Currently used in combination with other settings to construct URLs used in emails. Any URI that shares a host with `SITE_URL` is a permitted value for `redirect_to` params (see `/authorize` etc.). - -`URI_ALLOW_LIST` - `string` - -A comma separated list of URIs (e.g. `"https://foo.example.com,https://*.foo.example.com,https://bar.example.com"`) which are permitted as valid `redirect_to` destinations. Defaults to []. Supports wildcard matching through globbing. e.g. `https://*.foo.example.com` will allow `https://a.foo.example.com` and `https://b.foo.example.com` to be accepted. Globbing is also supported on subdomains. e.g. `https://foo.example.com/*` will allow `https://foo.example.com/page1` and `https://foo.example.com/page2` to be accepted. - -For more common glob patterns, check out the [following link](https://pkg.go.dev/github.com/gobwas/glob#Compile). - -`OPERATOR_TOKEN` - `string` _Multi-instance mode only_ - -The shared secret with an operator (usually Netlify) for this microservice. Used to verify requests have been proxied through the operator and -the payload values can be trusted. - -`DISABLE_SIGNUP` - `bool` - -When signup is disabled the only way to create new users is through invites. Defaults to `false`, all signups enabled. - -`GOTRUE_EXTERNAL_EMAIL_ENABLED` - `bool` - -Use this to disable email signups (users can still use external oauth providers to sign up / sign in) - -`GOTRUE_EXTERNAL_PHONE_ENABLED` - `bool` - -Use this to disable phone signups (users can still use external oauth providers to sign up / sign in) - -`GOTRUE_RATE_LIMIT_HEADER` - `string` - -Header on which to rate limit the `/token` endpoint. - -`GOTRUE_RATE_LIMIT_EMAIL_SENT` - `string` - -Rate limit the number of emails sent per hr on the following endpoints: `/signup`, `/invite`, `/magiclink`, `/recover`, `/otp`, & `/user`. - -`GOTRUE_PASSWORD_MIN_LENGTH` - `int` - -Minimum password length, defaults to 6. - -`GOTRUE_SECURITY_REFRESH_TOKEN_ROTATION_ENABLED` - `bool` - -If refresh token rotation is enabled, gotrue will automatically detect malicious attempts to reuse a revoked refresh token. When a malicious attempt is detected, gotrue immediately revokes all tokens that descended from the offending token. - -`GOTRUE_SECURITY_REFRESH_TOKEN_REUSE_INTERVAL` - `string` - -This setting is only applicable if `GOTRUE_SECURITY_REFRESH_TOKEN_ROTATION_ENABLED` is enabled. The reuse interval for a refresh token allows for exchanging the refresh token multiple times during the interval to support concurrency or offline issues. During the reuse interval, gotrue will not consider using a revoked token as a malicious attempt and will simply return the child refresh token. - -Only the previous revoked token can be reused. Using an old refresh token way before the current valid refresh token will trigger the reuse detection. +| Parameter | Type | Description | +| :-------- | :------- | :------------------------- | +| `REFRESH_TOKEN_ROTATION_ENABLED` | `bool` | If refresh token rotation is enabled, gotrue will automatically detect malicious attempts to reuse a revoked refresh token. When a malicious attempt is detected, gotrue immediately revokes all tokens that descended from the offending token. | +| `REFRESH_TOKEN_REUSE_INTERVAL` | `string` | This setting is only applicable if `REFRESH_TOKEN_ROTATION_ENABLED` is enabled. The reuse interval for a refresh token allows for exchanging the refresh token multiple times during the interval to support concurrency or offline issues.
During the reuse interval, gotrue will not consider using a revoked token as a malicious attempt and will simply return the child refresh token.
Only the previous revoked token can be reused. Using an old refresh token way before the current valid refresh token will trigger the reuse detection. | +| `CAPTCHA_ENABLED` | `string` | Enables the captcha middleware. | +| `CAPTCHA_PROVIDER` | `string` | The only captcha provider option supported is: `hcaptcha`. | +| `CAPTCHA_SECRET` | `string` | The captcha secret token. Retrieve this from your captcha account. | +| `CAPTCHA_TIMEOUT` | `string` | The http timeout on the captcha request. | +| `UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION` | `bool` | When enabled, this requires a user to reauthenticate before being able to update their password. | ### API -```properties -GOTRUE_API_HOST=localhost -PORT=9999 -``` +| Parameter | Type | Description | +| :-------- | :------- | :------------------------- | +| `API_HOST` | `string` | Hostname to listen on. | +| `PORT` | `number` | Port number to listen on. Defaults to `8081`. | +| `REQUEST_ID_HEADER` | `string` | If you wish to inherit a request ID from the incoming request, specify the name in this value. | -`API_HOST` - `string` - -Hostname to listen on. - -`PORT` (no prefix) / `API_PORT` - `number` - -Port number to listen on. Defaults to `8081`. - -`API_ENDPOINT` - `string` _Multi-instance mode only_ - -Controls what endpoint Netlify can access this API on. - -`REQUEST_ID_HEADER` - `string` - -If you wish to inherit a request ID from the incoming request, specify the name in this value. ### Database -```properties -GOTRUE_DB_DRIVER=mysql -DATABASE_URL=root@localhost/gotrue -``` - -`DB_DRIVER` - `string` **required** - -Chooses what dialect of database you want. Must be `mysql`. - -`DATABASE_URL` (no prefix) / `DB_DATABASE_URL` - `string` **required** - -Connection string for the database. - -`GOTRUE_DB_MAX_POOL_SIZE` - `int` - -Sets the maximum number of open connections to the database. Defaults to 0 which is equivalent to an "unlimited" number of connections. - -`DB_NAMESPACE` - `string` - -Adds a prefix to all table names. - -**Migrations Note** - -Migrations are not applied automatically, so you will need to run them after -you've built gotrue. - -- If built locally: `./gotrue migrate` -- Using Docker: `docker run --rm gotrue gotrue migrate` - -### Logging - -```properties -LOG_LEVEL=debug # available without GOTRUE prefix (exception) -GOTRUE_LOG_FILE=/var/log/go/gotrue.log -``` - -`LOG_LEVEL` - `string` - -Controls what log levels are output. Choose from `panic`, `fatal`, `error`, `warn`, `info`, or `debug`. Defaults to `info`. - -`LOG_FILE` - `string` - -If you wish logs to be written to a file, set `log_file` to a valid file path. - -### Opentracing - -Currently, only the Datadog tracer is supported. - -```properties -GOTRUE_TRACING_ENABLED=true -GOTRUE_TRACING_HOST=127.0.0.1 -GOTRUE_TRACING_PORT=8126 -GOTRUE_TRACING_TAGS="tag1:value1,tag2:value2" -GOTRUE_SERVICE_NAME="gotrue" -``` - -`TRACING_ENABLED` - `bool` - -Whether tracing is enabled or not. Defaults to `false`. - -`TRACING_HOST` - `bool` - -The tracing destination. - -`TRACING_PORT` - `bool` - -The port for the tracing host. - -`TRACING_TAGS` - `string` - -A comma separated list of key:value pairs. These key value pairs will be added as tags to all opentracing spans. - -`SERVICE_NAME` - `string` - -The name to use for the service. +| Parameter | Type | Description | +| :-------- | :------- | :------------------------- | +| `DB_DRIVER` | `string` | **Required**. Chooses what dialect of database you want. Must be `postgres`. | +| `DATABASE_URL` | `string` | **Required**. Connection string for the database. | +| `DB_MAX_POOL_SIZE` | `int` | Sets the maximum number of open connections to the database. Defaults to 0 which is equivalent to an "unlimited" number of connections. | +| `DB_NAMESPACE` | `string` | Specifies the schema in which the tables are to be created in. | ### JSON Web Tokens (JWT) -```properties -GOTRUE_JWT_SECRET=supersecretvalue -GOTRUE_JWT_EXP=3600 -GOTRUE_JWT_AUD=netlify -``` - -`JWT_SECRET` - `string` **required** - -The secret used to sign JWT tokens with. - -`JWT_EXP` - `number` - -How long tokens are valid for, in seconds. Defaults to 3600 (1 hour). - -`JWT_AUD` - `string` - -The default JWT audience. Use audiences to group users. - -`JWT_ADMIN_GROUP_NAME` - `string` - -The name of the admin group (if enabled). Defaults to `admin`. - -`JWT_DEFAULT_GROUP_NAME` - `string` - -The default group to assign all new users to. +| Parameter | Type | Description | +| :-------- | :------- | :------------------------- | +| `JWT_SECRET` | `string` | **Required**. The secret used to sign JWT tokens with. | +| `JWT_EXP` | `number` | How long tokens are valid for in seconds. Defaults to 3600 (1 hour). | +| `JWT_AUD` | `string` | The default JWT audience. Use audiences to group users. Defaults to `authenticated`. | +| `JWT_ADMIN_GROUP_NAME` | `string` | The name of the admin group (if enabled). Defaults to `admin`. | +| `JWT_DEFAULT_GROUP_NAME` | `string` | The default group to assign all new users to. | ### External Authentication Providers @@ -228,174 +77,74 @@ We support `apple`, `azure`, `bitbucket`, `discord`, `facebook`, `github`, `gitl Use the names as the keys underneath `external` to configure each separately. -```properties -GOTRUE_EXTERNAL_GITHUB_ENABLED=true -GOTRUE_EXTERNAL_GITHUB_CLIENT_ID=myappclientid -GOTRUE_EXTERNAL_GITHUB_SECRET=clientsecretvaluessssh -GOTRUE_EXTERNAL_GITHUB_REDIRECT_URI=http://localhost:3000/callback -``` - No external providers are required, but you must provide the required values if you choose to enable any. -`EXTERNAL_X_ENABLED` - `bool` - -Whether this external provider is enabled or not - -`EXTERNAL_X_CLIENT_ID` - `string` **required** - -The OAuth2 Client ID registered with the external provider. - -`EXTERNAL_X_SECRET` - `string` **required** - -The OAuth2 Client Secret provided by the external provider when you registered. - -`EXTERNAL_X_REDIRECT_URI` - `string` **required** - -The URI a OAuth2 provider will redirect to with the `code` and `state` values. - -`EXTERNAL_X_URL` - `string` - -The base URL used for constructing the URLs to request authorization and access tokens. Used by `gitlab` and `keycloak`. For `gitlab` it defaults to `https://gitlab.com`. For `keycloak` you need to set this to your instance, for example: `https://keycloak.example.com/auth/realms/myrealm` +| Parameter | Type | Description | +| :-------- | :------- | :------------------------- | +| `EXTERNAL_X_ENABLED` | `bool` | Whether this external provider is enabled or not | +| `EXTERNAL_X_CLIENT_ID` | `string` | **Required**. The OAuth2 Client ID registered with the external provider. | +| `EXTERNAL_X_SECRET` | `string` | **Required**. The OAuth2 Client Secret provided by the external provider when you registered. | +| `EXTERNAL_X_REDIRECT_URI` | `string` | **Required**. Also known as the callback url, this is the URI an OAuth2 provider will redirect to with the `code` and `state` values. | +| `EXTERNAL_X_URL` | `string` | The base URL used for constructing the URLs to request authorization and access tokens. Used by `gitlab` and `keycloak`. For `gitlab` it defaults to `https://gitlab.com`. For `keycloak` you need to set this to your realm url, (e.g. `https://keycloak.example.com/auth/realms/myrealm`) | #### Apple OAuth -To try out external authentication with Apple locally, you will need to do the following: +To try out sign-in with Apple locally, you will need to do the following: -1. Remap localhost to \ in your `/etc/hosts` config. -2. Configure gotrue to serve HTTPS traffic over localhost by replacing `ListenAndServe` in [api.go](api/api.go) with: - ``` - func (a *API) ListenAndServe(hostAndPort string) { - log := logrus.WithField("component", "api") - path, err := os.Getwd() - if err != nil { - log.Println(err) - } - server := &http.Server{ - Addr: hostAndPort, - Handler: a.handler, - } - done := make(chan struct{}) - defer close(done) - go func() { - waitForTermination(log, done) - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - server.Shutdown(ctx) - }() - if err := server.ListenAndServeTLS("PATH_TO_CRT_FILE", "PATH_TO_KEY_FILE"); err != http.ErrServerClosed { - log.WithError(err).Fatal("http server listen failed") - } - } - ``` -3. Generate the crt and key file. See [here](https://www.freecodecamp.org/news/how-to-get-https-working-on-your-local-development-environment-in-5-minutes-7af615770eec/) for more information. -4. Generate the `GOTRUE_EXTERNAL_APPLE_SECRET` by following this [post](https://medium.com/identity-beyond-borders/how-to-configure-sign-in-with-apple-77c61e336003)! +1. Run gotrue over https via ngrok or another proxy. +2. Generate the crt and key file. See [here](https://www.freecodecamp.org/news/how-to-get-https-working-on-your-local-development-environment-in-5-minutes-7af615770eec/) for more information. +3. Generate the `GOTRUE_EXTERNAL_APPLE_SECRET` by following this [post](https://medium.com/identity-beyond-borders/how-to-configure-sign-in-with-apple-77c61e336003)! -### E-Mail +### SMTP Configuration -Sending email is not required, but highly recommended for password recovery. -If enabled, you must provide the required values below. +Sending email is not required, but highly recommended for password recovery. If `mailer_autoconfirm` is enabled, no emails will be sent out for `signup`, `recovery`, `invite`. Emails will only be sent out for `magiclink`. -```properties -GOTRUE_SMTP_HOST=smtp.mandrillapp.com -GOTRUE_SMTP_PORT=587 -GOTRUE_SMTP_USER=smtp-delivery@example.com -GOTRUE_SMTP_PASS=correcthorsebatterystaple -GOTRUE_SMTP_ADMIN_EMAIL=support@example.com -GOTRUE_MAILER_SUBJECTS_CONFIRMATION="Please confirm" -``` +| Parameter | Type | Description | +| :-------- | :------- | :------------------------- | +| `SMTP_ADMIN_EMAIL` | `string` | **Required**. The `From` email address for all emails sent. | +| `SMTP_HOST` | `string` | **Required**. The mail server hostname to send emails through. | +| `SMTP_PORT` | `string` | **Required**. The port number to connect to the mail server on. | +| `SMTP_USER` | `string` | **Required**. If the mail server requires authentication, the username to use. | +| `SMTP_PASS` | `string` | **Required**. If the mail server requires authentication, the password to use. | +| `SMTP_MAX_FREQUENCY` | `string` | Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email. The value is the number of seconds. Defaults to 60 seconds. | +| `SMTP_SENDER_NAME` | `string` | Sets the name of the sender. Defaults to the `SMTP_ADMIN_EMAIL` if not used. | -`SMTP_ADMIN_EMAIL` - `string` **required** +### Email -The `From` email address for all emails sent. +These options control the emails sent from gotrue. -`SMTP_HOST` - `string` **required** +| Parameter | Type | Description | +| :-------- | :------- | :------------------------- | +| `MAILER_AUTOCONFIRM` | `bool` | If you do not require email confirmation, you may set this to `true`. Defaults to `false`. | +| `MAILER_OTP_EXP` | `string` | Controls the duration an email link or otp is valid for. Defaults to 24 hrs. | +| `MAILER_URLPATHS_INVITE` | `string` | URL path to use in the user invite email. Defaults to `/`. | +| `MAILER_URLPATHS_CONFIRMATION` | `string` | URL path to use in the signup confirmation email. Defaults to `/`. | +| `MAILER_URLPATHS_RECOVERY` | `string` | URL path to use in the password reset email. Defaults to `/`. | +| `MAILER_URLPATHS_EMAIL_CHANGE` | `string` | URL path to use in the email change confirmation email. Defaults to `/`. | +| `MAILER_SUBJECTS_INVITE` | `string` | Email subject to use for user invite. Defaults to `You have been invited`. | +| `MAILER_SUBJECTS_CONFIRMATION` | `string` | Email subject to use for signup confirmation. Defaults to `Confirm Your Signup`. | +| `MAILER_SUBJECTS_RECOVERY` | `string` | Email subject to use for password reset. Defaults to `Reset Your Password`. | +| `MAILER_SUBJECTS_MAGIC_LINK` | `string` | Email subject to use for magic link email. Defaults to `Your Magic Link`. | +| `MAILER_SUBJECTS_EMAIL_CHANGE` | `string` | Email subject to use for email change confirmation. Defaults to `Confirm Email Change`. | -The mail server hostname to send emails through. - -`SMTP_PORT` - `number` **required** - -The port number to connect to the mail server on. - -`SMTP_USER` - `string` - -If the mail server requires authentication, the username to use. - -`SMTP_PASS` - `string` - -If the mail server requires authentication, the password to use. - -`SMTP_MAX_FREQUENCY` - `number` - -Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email. The value is the number of seconds. Defaults to 900 (15 minutes). - -`SMTP_SENDER_NAME` - `string` - -Sets the name of the sender. Defaults to the `SMTP_ADMIN_EMAIL` if not used. - -`MAILER_AUTOCONFIRM` - `bool` - -If you do not require email confirmation, you may set this to `true`. Defaults to `false`. - -`MAILER_OTP_EXP` - `number` - -Controls the duration an email link or otp is valid for. - -`MAILER_URLPATHS_INVITE` - `string` - -URL path to use in the user invite email. Defaults to `/`. - -`MAILER_URLPATHS_CONFIRMATION` - `string` - -URL path to use in the signup confirmation email. Defaults to `/`. - -`MAILER_URLPATHS_RECOVERY` - `string` - -URL path to use in the password reset email. Defaults to `/`. - -`MAILER_URLPATHS_EMAIL_CHANGE` - `string` - -URL path to use in the email change confirmation email. Defaults to `/`. - -`MAILER_SUBJECTS_INVITE` - `string` - -Email subject to use for user invite. Defaults to `You have been invited`. - -`MAILER_SUBJECTS_CONFIRMATION` - `string` - -Email subject to use for signup confirmation. Defaults to `Confirm Your Signup`. - -`MAILER_SUBJECTS_RECOVERY` - `string` - -Email subject to use for password reset. Defaults to `Reset Your Password`. - -`MAILER_SUBJECTS_MAGIC_LINK` - `string` - -Email subject to use for magic link email. Defaults to `Your Magic Link`. - -`MAILER_SUBJECTS_EMAIL_CHANGE` - `string` - -Email subject to use for email change confirmation. Defaults to `Confirm Email Change`. - -`MAILER_TEMPLATES_INVITE` - `string` - -URL path to an email template to use when inviting a user. -`SiteURL`, `Email`, and `ConfirmationURL` variables are available. +### Email Templates +| Parameter | Type | Description | +| :-------- | :------- | :------------------------- | +| `MAILER_TEMPLATES_INVITE` | `string` | URL path to an email template to use when inviting a user. `SiteURL`, `Email`, and `ConfirmationURL` variables are available. | Default Content (if template is unavailable): -```html -

You have been invited

- +```html +

You have been invited

You have been invited to create a user on {{ .SiteURL }}. Follow this link to accept the invite:

Accept the invite

``` -`MAILER_TEMPLATES_CONFIRMATION` - `string` - -URL path to an email template to use when confirming a signup. -`SiteURL`, `Email`, and `ConfirmationURL` variables are available. +| Parameter | Type | Description | +| :-------- | :------- | :------------------------- | +| `MAILER_TEMPLATES_CONFIRMATION` | `string` | URL path to an email template to use when confirming a signup. `SiteURL`, `Email`, and `ConfirmationURL` variables are available. | Default Content (if template is unavailable): @@ -406,10 +155,9 @@ Default Content (if template is unavailable):

Confirm your mail

``` -`MAILER_TEMPLATES_RECOVERY` - `string` - -URL path to an email template to use when resetting a password. -`SiteURL`, `Email`, and `ConfirmationURL` variables are available. +| Parameter | Type | Description | +| :-------- | :------- | :------------------------- | +| `MAILER_TEMPLATES_RECOVERY` | `string` | URL path to an email template to use when resetting a password. `SiteURL`, `Email`, and `ConfirmationURL` variables are available. | Default Content (if template is unavailable): @@ -420,10 +168,9 @@ Default Content (if template is unavailable):

Reset Password

``` -`MAILER_TEMPLATES_MAGIC_LINK` - `string` - -URL path to an email template to use when sending magic link. -`SiteURL`, `Email`, and `ConfirmationURL` variables are available. +| Parameter | Type | Description | +| :-------- | :------- | :------------------------- | +| `MAILER_TEMPLATES_MAGIC_LINK` | `string` | URL path to an email template to use when sending magic link. `SiteURL`, `Email`, and `ConfirmationURL` variables are available. | Default Content (if template is unavailable): @@ -434,10 +181,9 @@ Default Content (if template is unavailable):

Log In

``` -`MAILER_TEMPLATES_EMAIL_CHANGE` - `string` - -URL path to an email template to use when confirming the change of an email address. -`SiteURL`, `Email`, `NewEmail`, and `ConfirmationURL` variables are available. +| Parameter | Type | Description | +| :-------- | :------- | :------------------------- | +| `MAILER_TEMPLATES_EMAIL_CHANGE` | `string` | URL path to an email template to use when confirming the change of an email address. `SiteURL`, `Email`, `NewEmail` and `ConfirmationURL` variables are available. | Default Content (if template is unavailable): @@ -448,549 +194,62 @@ Default Content (if template is unavailable):

Change Email

``` -`WEBHOOK_URL` - `string` - -Url of the webhook receiver endpoint. This will be called when events like `validate`, `signup` or `login` occur. - -`WEBHOOK_SECRET` - `string` - -Shared secret to authorize webhook requests. This secret signs the [JSON Web Signature](https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41) of the request. You _should_ use this to verify the integrity of the request. Otherwise others can feed your webhook receiver with fake data. - -`WEBHOOK_RETRIES` - `number` - -How often GoTrue should try a failed hook. - -`WEBHOOK_TIMEOUT_SEC` - `number` - -Time between retries (in seconds). - -`WEBHOOK_EVENTS` - `list` - -Which events should trigger a webhook. You can provide a comma separated list. -For example to listen to all events, provide the values `validate,signup,login`. - ### Phone Auth -`SMS_AUTOCONFIRM` - `bool` +These options control the sms-es sent from gotrue. -If you do not require phone confirmation, you may set this to `true`. Defaults to `false`. +| Parameter | Type | Description | +| :-------- | :------- | :------------------------- | +| `SMS_AUTOCONFIRM` | `bool` | If you do not require phone confirmation, you may set this to `true`. Defaults to `false`. | +| `SMS_MAX_FREQUENCY` | `number` | Controls the minimum amount of time that must pass before sending another sms otp. The value is the number of seconds. Defaults to 60 (1 minute)). | +| `SMS_OTP_EXP` | `number` | Controls the duration an sms otp is valid for. | +| `SMS_OTP_LENGTH` | `number` | Controls the number of digits of the sms otp sent. Valid otp lengths are between [6 - 10] digits | +| `SMS_PROVIDER` | `string` | Available options are: `twilio`, `messagebird`, `textlocal`, and `vonage` | -`SMS_MAX_FREQUENCY` - `number` +If you're using twilio, you can obtain your credentials from the [twilio dashboard](https://www.twilio.com/docs/usage/requests-to-twilio#credentials): -Controls the minimum amount of time that must pass before sending another sms otp. The value is the number of seconds. Defaults to 60 (1 minute)). +You can find the `SMS_TWILIO_ACCOUNT_SID` and `SMS_TWILIO_AUTH_TOKEN` in the account info page of the twilio console dashboard. -`SMS_OTP_EXP` - `number` +| Parameter | Type | Description | +| :-------- | :------- | :------------------------- | +| `SMS_TWILIO_ACCOUNT_SID` | `string` | Your twilio account string identifier (SID). | +| `SMS_TWILIO_AUTH_TOKEN` | `string` | Your twilio auth token. | +| `SMS_TWILIO_MESSAGE_SERVICE_SID` | `string` | Your twilio sender mobile number | -Controls the duration an sms otp is valid for. -`SMS_OTP_LENGTH` - `number` +If you're using messagebird, you can obtain your credentials from the [messagebird dashboard](https://dashboard.messagebird.com/en/developers/access): -Controls the number of digits of the sms otp sent. +| Parameter | Type | Description | +| :-------- | :------- | :------------------------- | +| `SMS_MESSAGEBIRD_ACCESS_KEY` | `string` | Your messagebird access key. | +| `SMS_MESSAGEBIRD_ORIGINATOR` | `string` | Your messagebird sender phone number with + or company name. | -`SMS_PROVIDER` - `string` +### Logging -Available options are: `twilio`, `messagebird`, `textlocal`, and `vonage` +| Parameter | Type | Description | +| :-------- | :------- | :------------------------- | +| `LOG_LEVEL` | `string` | Controls what log levels are output. Choose from `panic`, `fatal`, `error`, `warn`, `info`, or `debug`. Defaults to `info`. | +| `LOG_FILE` | `string` | If you wish logs to be written to a file, set `log_file` to a valid file path. | -Then you can use your [twilio credentials](https://www.twilio.com/docs/usage/requests-to-twilio#credentials): +### Opentracing -- `SMS_TWILIO_ACCOUNT_SID` -- `SMS_TWILIO_AUTH_TOKEN` -- `SMS_TWILIO_MESSAGE_SERVICE_SID` - can be set to your twilio sender mobile number +Currently, only the Datadog tracer is supported. -Or Messagebird credentials, which can be obtained in the [Dashboard](https://dashboard.messagebird.com/en/developers/access): +| Parameter | Type | Description | +| :-------- | :------- | :------------------------- | +| `TRACING_ENABLED` | `bool` | Whether tracing is enabled or not. Defaults to `false`. | +| `TRACING_HOST` | `string` | The tracing destination. (e.g. `GOTRUE_TRACING_HOST=127.0.0.1`) | +| `TRACING_PORT` | `int` | The port for the tracing host. | +| `TRACING_TAGS` | `string` | A comma separated list of key:value pairs. These key value pairs will be added as tags to all opentracing spans. (e.g. `GOTRUE_TRACING_TAGS="tag1:value1,tag2:value2"`) | +| `SERVICE_NAME` | `string` | The name to use for the service. | -- `SMS_MESSAGEBIRD_ACCESS_KEY` - your Messagebird access key -- `SMS_MESSAGEBIRD_ORIGINATOR` - SMS sender (your Messagebird phone number with + or company name) +### Webhooks -### CAPTCHA +| Parameter | Type | Description | +| :-------- | :------- | :------------------------- | +| `WEBHOOK_URL` | `string` | Url of the webhook receiver endpoint. This will be called when events like `validate`, `signup` or `login` occur. | +| `WEBHOOK_SECRET` | `string` | Shared secret to authorize webhook requests. This secret signs the [JSON Web Signature](https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41) of the request. You _should_ use this to verify the integrity of the request. Otherwise others can feed your webhook receiver with fake data. | +| `WEBHOOK_RETRIES` | `number` | How often GoTrue should try a failed hook. | +| `WEBHOOK_TIMEOUT_SEC` | `number` | Time between retries (in seconds). | +| `WEBHOOK_EVENTS` | `list` | Which events should trigger a webhook. You can provide a comma separated list. For example to listen to all events, provide the values `validate,signup,login`. | -- If enabled, CAPTCHA will check the request body for the `hcaptcha_token` field and make a verification request to the CAPTCHA provider. - -`SECURITY_CAPTCHA_ENABLED` - `string` - -Whether captcha middleware is enabled - -`SECURITY_CAPTCHA_PROVIDER` - `string` - -for now the only option supported is: `hcaptcha` - -- `SECURITY_CAPTCHA_SECRET` - `string` -- `SECURITY_CAPTCHA_TIMEOUT` - `string` - -Retrieve from hcaptcha account - -### Reauthentication - -`SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION` - `bool` - -Enforce reauthentication on password update. - -## Endpoints - -GoTrue exposes the following endpoints: - -### **GET /settings** - -Returns the publicly available settings for this gotrue instance. - -```json -{ - "external": { - "apple": true, - "azure": true, - "bitbucket": true, - "discord": true, - "facebook": true, - "github": true, - "gitlab": true, - "google": true, - "keycloak": true, - "linkedin": true, - "notion": true, - "slack": true, - "spotify": true, - "twitch": true, - "twitter": true, - "workos": true - }, - "disable_signup": false, - "autoconfirm": false -} -``` - -### **POST, PUT /admin/users/** - -Creates (POST) or Updates (PUT) the user based on the `user_id` specified. The `ban_duration` field accepts the following time units: "ns", "us", "ms", "s", "m", "h". See [`time.ParseDuration`](https://pkg.go.dev/time#ParseDuration) for more details on the format used. - -```js -headers: -{ - "Authorization": "Bearer eyJhbGciOiJI...M3A90LCkxxtX9oNP9KZO" // admin role required -} - -body: -{ - "role": "test-user", - "email": "email@example.com", - "phone": "12345678", - "password": "secret", // only if type = signup - "email_confirm": true, - "phone_confirm": true, - "user_metadata": {}, - "app_metadata": {}, - "ban_duration": "24h" or "none" // to unban a user -} -``` - -### **POST /admin/generate_link** - -Returns the corresponding email action link based on the type specified. - -```js -headers: -{ - "Authorization": "Bearer eyJhbGciOiJI...M3A90LCkxxtX9oNP9KZO" // admin role required -} - -body: -{ - "type": "signup" or "magiclink" or "recovery" or "invite", - "email": "email@example.com", - "password": "secret", // only if type = signup - "data": { - ... - }, // only if type = signup - "redirect_to": "https://supabase.io" // Redirect URL to send the user to after an email action. Defaults to SITE_URL. - -} -``` - -Returns - -```js -{ - "action_link": "http://localhost:9999/verify?token=TOKEN&type=TYPE&redirect_to=REDIRECT_URL", - ... -} -``` - -### **POST /signup** - -Register a new user with an email and password. - -```js -{ - "email": "email@example.com", - "password": "secret" -} -``` - -returns: - -```json -{ - "id": "11111111-2222-3333-4444-5555555555555", - "email": "email@example.com", - "confirmation_sent_at": "2016-05-15T20:49:40.882805774-07:00", - "created_at": "2016-05-15T19:53:12.368652374-07:00", - "updated_at": "2016-05-15T19:53:12.368652374-07:00" -} - -// if sign up is a duplicate then faux data will be returned -// as to not leak information about whether a given email -// has an account with your service or not -``` - -Register a new user with a phone number and password. - -```js -{ - "phone": "12345678", // follows the E.164 format - "password": "secret" -} -``` - -Returns: - -```json -{ - "id": "11111111-2222-3333-4444-5555555555555", // if duplicate sign up, this ID will be faux - "phone": "12345678", - "confirmation_sent_at": "2016-05-15T20:49:40.882805774-07:00", - "created_at": "2016-05-15T19:53:12.368652374-07:00", - "updated_at": "2016-05-15T19:53:12.368652374-07:00" -} -``` - -if AUTOCONFIRM is enabled and the sign up is a duplicate, then the endpoint will return: - -``` -{ - "code":400, - "msg":"User already registered" -} -``` - -### **POST /invite** - -Invites a new user with an email. -This endpoint requires the `service_role` or `supabase_admin` JWT set as an Auth Bearer header: - -e.g. - -```json -headers: { - "Authorization" : "Bearer eyJhbGciOiJI...M3A90LCkxxtX9oNP9KZO" -} -``` - -```json -{ - "email": "email@example.com" -} -``` - -Returns: - -```json -{ - "id": "11111111-2222-3333-4444-5555555555555", - "email": "email@example.com", - "confirmation_sent_at": "2016-05-15T20:49:40.882805774-07:00", - "created_at": "2016-05-15T19:53:12.368652374-07:00", - "updated_at": "2016-05-15T19:53:12.368652374-07:00", - "invited_at": "2016-05-15T19:53:12.368652374-07:00" -} -``` - -### **POST /verify** - -Verify a registration or a password recovery. Type can be `signup` or `recovery` or `invite` -and the `token` is a token returned from either `/signup` or `/recover`. - -```json -{ - "type": "signup", - "token": "confirmation-code-delivered-in-email" -} -``` - -`password` is required for signup verification if no existing password exists. - -Returns: - -```json -{ - "access_token": "jwt-token-representing-the-user", - "token_type": "bearer", - "expires_in": 3600, - "refresh_token": "a-refresh-token", - "type": "signup | recovery | invite" -} -``` - -Verify a phone signup or sms otp. Type should be set to `sms`. - -```json -{ - "type": "sms", - "token": "confirmation-otp-delivered-in-sms", - "redirect_to": "https://supabase.io", - "phone": "phone-number-sms-otp-was-delivered-to" -} -``` - -Returns: - -```json -{ - "access_token": "jwt-token-representing-the-user", - "token_type": "bearer", - "expires_in": 3600, - "refresh_token": "a-refresh-token" -} -``` - -### **GET /verify** - -Verify a registration or a password recovery. Type can be `signup` or `recovery` or `magiclink` or `invite` -and the `token` is a token returned from either `/signup` or `/recover` or `/magiclink`. - -query params: - -```json -{ - "type": "signup", - "token": "confirmation-code-delivered-in-email", - "redirect_to": "https://supabase.io" -} -``` - -User will be logged in and redirected to: - -```json -SITE_URL/#access_token=jwt-token-representing-the-user&token_type=bearer&expires_in=3600&refresh_token=a-refresh-token&type=invite -``` - -Your app should detect the query params in the fragment and use them to set the session (supabase-js does this automatically) - -You can use the `type` param to redirect the user to a password set form in the case of `invite` or `recovery`, -or show an account confirmed/welcome message in the case of `signup`, or direct them to some additional onboarding flow - -### **POST /otp** - -One-Time-Password. Will deliver a magiclink or sms otp to the user depending on whether the request body contains an "email" or "phone" key. - -If `"create_user": true`, user will not be automatically signed up if the user doesn't exist. - -```js -{ - "phone": "12345678" // follows the E.164 format - "create_user": true -} - -OR - -// exactly the same as /magiclink -{ - "email": "email@example.com" - "create_user": true -} -``` - -Returns: - -``` -{} -``` - -### **POST /magiclink** (recommended to use /otp instead. See above.) - -Magic Link. Will deliver a link (e.g. `/verify?type=magiclink&token=fgtyuf68ddqdaDd`) to the user based on -email address which they can use to redeem an access_token. - -By default Magic Links can only be sent once every 60 seconds - -```json -{ - "email": "email@example.com" -} -``` - -Returns: - -```json -{} -``` - -when clicked the magic link will redirect the user to `#access_token=x&refresh_token=y&expires_in=z&token_type=bearer&type=magiclink` (see `/verify` above) - -### **POST /recover** - -Password recovery. Will deliver a password recovery mail to the user based on -email address. - -By default recovery links can only be sent once every 60 seconds - -```json -{ - "email": "email@example.com" -} -``` - -Returns: - -```json -{} -``` - -### **POST /token** - -This is an OAuth2 endpoint that currently implements -the password and refresh_token grant types - -query params: - -``` -?grant_type=password -``` - -body: - -```json -// Email login -{ - "email": "name@domain.com", - "password": "somepassword" -} - -// Phone login -{ - "phone": "12345678", - "password": "somepassword" -} -``` - -or - -query params: - -``` -grant_type=refresh_token -``` - -body: - -```json -{ - "refresh_token": "a-refresh-token" -} -``` - -Once you have an access token, you can access the methods requiring authentication -by settings the `Authorization: Bearer YOUR_ACCESS_TOKEN_HERE` header. - -Returns: - -```json -{ - "access_token": "jwt-token-representing-the-user", - "token_type": "bearer", - "expires_in": 3600, - "refresh_token": "a-refresh-token" -} -``` - -### **GET /user** - -Get the JSON object for the logged in user (requires authentication) - -Returns: - -```json -{ - "id": "11111111-2222-3333-4444-5555555555555", - "email": "email@example.com", - "confirmation_sent_at": "2016-05-15T20:49:40.882805774-07:00", - "created_at": "2016-05-15T19:53:12.368652374-07:00", - "updated_at": "2016-05-15T19:53:12.368652374-07:00" -} -``` - -### **PUT /user** - -Update a user (Requires authentication). Apart from changing email/password, this -method can be used to set custom user data. Changing the email will result in a magiclink being sent out. - -```json -{ - "email": "new-email@example.com", - "password": "new-password", - "data": { - "key": "value", - "number": 10, - "admin": false - } -} -``` - -Returns: - -```json -{ - "id": "11111111-2222-3333-4444-5555555555555", - "email": "email@example.com", - "email_change_sent_at": "2016-05-15T20:49:40.882805774-07:00", - "created_at": "2016-05-15T19:53:12.368652374-07:00", - "updated_at": "2016-05-15T19:53:12.368652374-07:00" -} -``` - -If `GOTRUE_SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION` is enabled, the user will need to reauthenticate first. - -```json -{ - "password": "new-password", - "nonce": "123456" -} -``` - -### **GET /reauthenticate** - -Sends a nonce to the user's email (preferred) or phone. This endpoint requires the user to be logged in / authenticated first. The user needs to have either an email or phone number for the nonce to be sent successfully. - -```json -headers: { - "Authorization" : "Bearer eyJhbGciOiJI...M3A90LCkxxtX9oNP9KZO" -} -``` - -### **POST /logout** - -Logout a user (Requires authentication). - -This will revoke all refresh tokens for the user. Remember that the JWT tokens -will still be valid for stateless auth until they expires. - -### **GET /authorize** - -Get access_token from external oauth provider - -query params: - -``` -provider=apple | azure | bitbucket | discord | facebook | github | gitlab | google | keycloak | linkedin | notion | slack | spotify | twitch | twitter | workos - -scopes= -``` - -Redirects to provider and then to `/callback` - -For apple specific setup see: https://github.com/supabase/gotrue#apple-oauth - -### **GET /callback** - -External provider should redirect to here - -Redirects to `#access_token=&refresh_token=&provider_token=&expires_in=3600&provider=` -If additional scopes were requested then `provider_token` will be populated, you can use this to fetch additional data from the provider or interact with their services diff --git a/web/package-lock.json b/web/package-lock.json index b9cbc2c55f3..ddacffa51b3 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -8719,16 +8719,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "node_modules/lodash.assignin": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", - "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=" - }, - "node_modules/lodash.bind": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", - "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=" - }, "node_modules/lodash.curry": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", @@ -8739,66 +8729,16 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" }, - "node_modules/lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" - }, - "node_modules/lodash.filter": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", - "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=" - }, - "node_modules/lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" - }, "node_modules/lodash.flow": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", "integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=" }, - "node_modules/lodash.foreach": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", - "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" - }, - "node_modules/lodash.map": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", - "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=" - }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" - }, - "node_modules/lodash.pick": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", - "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=" - }, - "node_modules/lodash.reduce": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", - "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" - }, - "node_modules/lodash.reject": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", - "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=" - }, - "node_modules/lodash.some": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", - "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=" - }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -10800,6 +10740,18 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, + "node_modules/react-loadable": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-loadable/-/react-loadable-5.5.0.tgz", + "integrity": "sha512-C8Aui0ZpMd4KokxRdVAm2bQtI03k2RMRNzOB+IipV3yxFTSVICv7WoUr5L9ALB5BmKO1iHgZtWM8EvYG83otdg==", + "peer": true, + "dependencies": { + "prop-types": "^15.5.0" + }, + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-loadable-ssr-addon-v5-slorber": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz", @@ -15859,6 +15811,7 @@ }, "@kiwicopple/prism-react-renderer": { "version": "git+ssh://git@github.com/kiwicopple/prism-react-renderer.git#4a09100a587bce2d94d7ac8ed3564a61c6e70781", + "integrity": "sha512-QluokmJkMoHOwahIBN34hTG5n03kFyJ0REDq4mkf0o05JUC61ikXQsPNXtynASqoZRW7A+bKVNT65kCtUfgZlQ==", "from": "@kiwicopple/prism-react-renderer@https://github.com/kiwicopple/prism-react-renderer.git", "requires": { "prismjs": "^1.22.0" @@ -20417,16 +20370,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "lodash.assignin": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", - "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=" - }, - "lodash.bind": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", - "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=" - }, "lodash.curry": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", @@ -20437,66 +20380,16 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" }, - "lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" - }, - "lodash.filter": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", - "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=" - }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" - }, "lodash.flow": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", "integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=" }, - "lodash.foreach": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", - "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" - }, - "lodash.map": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", - "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=" - }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" - }, - "lodash.pick": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", - "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=" - }, - "lodash.reduce": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", - "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" - }, - "lodash.reject": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", - "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=" - }, - "lodash.some": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", - "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=" - }, "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -21880,6 +21773,15 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, + "react-loadable": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-loadable/-/react-loadable-5.5.0.tgz", + "integrity": "sha512-C8Aui0ZpMd4KokxRdVAm2bQtI03k2RMRNzOB+IipV3yxFTSVICv7WoUr5L9ALB5BmKO1iHgZtWM8EvYG83otdg==", + "peer": true, + "requires": { + "prop-types": "^15.5.0" + } + }, "react-loadable-ssr-addon-v5-slorber": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz", diff --git a/web/package.json b/web/package.json index 72777af1c46..335e2499296 100644 --- a/web/package.json +++ b/web/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "serve": "npm run start", - "start": "docusaurus start --port 3005", + "start": "docusaurus start --port 3006", "build": "npm run gen:all && docusaurus build", "swizzle": "docusaurus swizzle", "traction": "node traction.js", diff --git a/web/spec/combined.json b/web/spec/combined.json index 02d926a7d22..6b69c47f2c7 100644 --- a/web/spec/combined.json +++ b/web/spec/combined.json @@ -63359,7 +63359,7 @@ "isExported": true }, "comment": { - "text": "A unique identifier for the bucket you are creating.\n" + "text": "A unique identifier for the bucket you are updating.\n" }, "type": { "type": "intrinsic", @@ -64901,7 +64901,7 @@ "isExported": true }, "comment": { - "text": "A unique identifier for the bucket you are creating.\n" + "text": "A unique identifier for the bucket you are updating.\n" }, "type": { "type": "intrinsic", diff --git a/web/spec/storage.json b/web/spec/storage.json index 82c0be5f743..596f8be4763 100644 --- a/web/spec/storage.json +++ b/web/spec/storage.json @@ -1267,7 +1267,7 @@ "isExported": true }, "comment": { - "text": "A unique identifier for the bucket you are creating.\n" + "text": "A unique identifier for the bucket you are updating.\n" }, "type": { "type": "intrinsic", @@ -2809,7 +2809,7 @@ "isExported": true }, "comment": { - "text": "A unique identifier for the bucket you are creating.\n" + "text": "A unique identifier for the bucket you are updating.\n" }, "type": { "type": "intrinsic", From 83f0b4d92ef45b586f97c2196e3668a70536e6ff Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Fri, 17 Jun 2022 15:52:53 -0230 Subject: [PATCH 23/57] Explicitly say that all keys will be reset --- studio/pages/project/[ref]/settings/api.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/pages/project/[ref]/settings/api.tsx b/studio/pages/project/[ref]/settings/api.tsx index a85c3bdf07e..7af9d5ed759 100644 --- a/studio/pages/project/[ref]/settings/api.tsx +++ b/studio/pages/project/[ref]/settings/api.tsx @@ -353,7 +353,7 @@ const ServiceList: FC = ({ projectRef }) => { variant="warning" title="This will invalidate all existing API keys!" > - Your project will also be restarted during this process, which will terminate + Generating a new JWT secret will invalidate all of your API keys, including your service_role and anon keys. Your project will also be restarted during this process, which will terminate any existing connections. )} From 6da91b46692a2ef4022aa2d5ae48c10ce3743665 Mon Sep 17 00:00:00 2001 From: Aniket Singh <31aniket.singh@gmail.com> Date: Sat, 18 Jun 2022 14:05:35 +0530 Subject: [PATCH 24/57] removed separate dir for auth and crud func && updated the supabase --- .../assets/supabase-dark.svg | 23 ---- .../assets/supabase-logo.png | Bin 0 -> 10860 bytes .../flutter-todo-list/lib/constant.dart | 3 +- .../flutter-todo-list/lib/functions/auth.dart | 31 ----- .../flutter-todo-list/lib/functions/crud.dart | 99 ---------------- .../todo-list/flutter-todo-list/lib/main.dart | 26 ++-- .../flutter-todo-list/lib/model.dart | 12 -- .../flutter-todo-list/lib/screens/signin.dart | 18 ++- .../flutter-todo-list/lib/screens/signup.dart | 36 +++++- .../flutter-todo-list/lib/screens/todo.dart | 111 ++++++++++++++---- .../todo-list/flutter-todo-list/pubspec.lock | 25 ++-- .../todo-list/flutter-todo-list/pubspec.yaml | 2 +- .../windows/flutter/generated_plugins.cmake | 8 -- 13 files changed, 159 insertions(+), 235 deletions(-) delete mode 100644 examples/todo-list/flutter-todo-list/assets/supabase-dark.svg create mode 100644 examples/todo-list/flutter-todo-list/assets/supabase-logo.png delete mode 100644 examples/todo-list/flutter-todo-list/lib/functions/auth.dart delete mode 100644 examples/todo-list/flutter-todo-list/lib/functions/crud.dart delete mode 100644 examples/todo-list/flutter-todo-list/lib/model.dart diff --git a/examples/todo-list/flutter-todo-list/assets/supabase-dark.svg b/examples/todo-list/flutter-todo-list/assets/supabase-dark.svg deleted file mode 100644 index 9e96fab4ddc..00000000000 --- a/examples/todo-list/flutter-todo-list/assets/supabase-dark.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/todo-list/flutter-todo-list/assets/supabase-logo.png b/examples/todo-list/flutter-todo-list/assets/supabase-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..70e14586dbe3f14a7eb1aeb7e6fea4758ee8ce9b GIT binary patch literal 10860 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 a8~hp4*tQ&|Mm-#T6i*AT535wQ2>pK@xh6RP literal 0 HcmV?d00001 diff --git a/examples/todo-list/flutter-todo-list/lib/constant.dart b/examples/todo-list/flutter-todo-list/lib/constant.dart index ef3ef3651cd..70abe48f080 100644 --- a/examples/todo-list/flutter-todo-list/lib/constant.dart +++ b/examples/todo-list/flutter-todo-list/lib/constant.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; const smallGap = SizedBox(height: 20); const largeGap = SizedBox(height: 40); - +final client = Supabase.instance.client; final appTheme = ThemeData.dark().copyWith( scaffoldBackgroundColor: Colors.black, appBarTheme: const AppBarTheme( diff --git a/examples/todo-list/flutter-todo-list/lib/functions/auth.dart b/examples/todo-list/flutter-todo-list/lib/functions/auth.dart deleted file mode 100644 index 14ff177ca9a..00000000000 --- a/examples/todo-list/flutter-todo-list/lib/functions/auth.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:supabase_flutter/supabase_flutter.dart'; - -class AuthSupabase { - static final client = Supabase.instance.client; - static Future createAccount({ - required String email, - required String password, - }) async { - final res = await client.auth.signUp(email, password); - final error = res.error; - if (error == null) { - return true; - } else { - return false; - } - } - - static Future loginUser({ - required String email, - required String password, - }) async { - final res = await client.auth.signIn(email: email, password: password); - final user = res.data?.user; - return user?.id; - } - - static Future logOut(BuildContext context) async { - await client.auth.signOut(); - } -} diff --git a/examples/todo-list/flutter-todo-list/lib/functions/crud.dart b/examples/todo-list/flutter-todo-list/lib/functions/crud.dart deleted file mode 100644 index 27ce0f63cfe..00000000000 --- a/examples/todo-list/flutter-todo-list/lib/functions/crud.dart +++ /dev/null @@ -1,99 +0,0 @@ -import 'package:todosupabase/functions/auth.dart'; - -class CrudSupabase { - static Future getUser({ - required String email, - }) async { - final res = await AuthSupabase.client - .from('Users') - .select() - .eq('email', email) - .execute(); - final data = res.data; - final error = res.error; - print(data); - return data; - } - - static Future> getTodo() async { - final res = await AuthSupabase.client - .from('Todo') - .select() - .eq('email', AuthSupabase.client.auth.currentUser!.email.toString()) - .execute(); - final data = res.data; - final error = res.error; - return data; - } - - static Future addTodo({ - required String task, - required String due, - }) async { - final res = await AuthSupabase.client.from('Todo').insert({ - 'email': AuthSupabase.client.auth.currentUser!.email.toString(), - 'task': task, - 'due': due, - 'done': false - }).execute(); - final data = res.data; - final error = res.error; - print(data); - return data; - } - - static Future deleteTodo({required String id}) async { - final res = - await AuthSupabase.client.from('Todo').delete().eq('id', id).execute(); - final data = res.data; - final error = res.error; - print(data); - return data; - } - - static Future taskDone({ - required String id, - required bool value, - }) async { - final res = await AuthSupabase.client - .from('Todo') - .update({'done': value}) - .eq('id', id) - .execute(); - final data = res.data; - final error = res.error; - print(data); - return data; - } - - static Future editTask({ - required String id, - required String due, - required String task, - }) async { - final res = await AuthSupabase.client - .from('Todo') - .update({'due': due, 'task': task}) - .eq('id', id) - .execute(); - final data = res.data; - final error = res.error; - print(data); - return data; - } - - static Future addUser({ - required String name, - required String email, - }) async { - final res = await AuthSupabase.client.from('Users').insert({ - 'name': name, - 'email': email, - }).execute(); - print('adding number'); - final data = res.data; - final error = res.error; - print(data); - print(error); - } -} diff --git a/examples/todo-list/flutter-todo-list/lib/main.dart b/examples/todo-list/flutter-todo-list/lib/main.dart index 0aaa0b1b843..ff4dcd5b46d 100644 --- a/examples/todo-list/flutter-todo-list/lib/main.dart +++ b/examples/todo-list/flutter-todo-list/lib/main.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:todosupabase/constant.dart'; -import 'package:todosupabase/functions/auth.dart'; import 'package:todosupabase/screens/signup.dart'; import 'package:todosupabase/screens/signin.dart'; import 'package:todosupabase/screens/todo.dart'; @@ -23,13 +21,12 @@ class MyApp extends StatelessWidget { return MaterialApp( debugShowCheckedModeBanner: false, theme: appTheme, - initialRoute: - AuthSupabase.client.auth.currentSession != null ? "/todo" : "/", + initialRoute: client.auth.currentSession != null ? '/todo' : '/', routes: { - "/": (context) => const MyHomePage(), - "/signup": (context) => const SignUpPage(), - "/signin": (context) => const SignInPage(), - "/todo": (context) => const TodoPage(), + '/': (context) => const MyHomePage(), + '/signup': (context) => const SignUpPage(), + '/signin': (context) => const SignInPage(), + '/todo': (context) => const TodoPage(), }, ); } @@ -53,19 +50,22 @@ class MyHomePageState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - SvgPicture.asset("assets/supabase-dark.svg", width: 200), + Image.asset( + 'assets/supabase-logo.png', + height: 40, + ), largeGap, OutlinedButton( - child: const Text("Sign In"), + child: const Text('Sign In'), onPressed: () { - Navigator.pushNamed(context, "/signin"); + Navigator.pushNamed(context, '/signin'); }, ), smallGap, OutlinedButton( - child: const Text("Sign Up"), + child: const Text('Sign Up'), onPressed: () { - Navigator.pushNamed(context, "/signup"); + Navigator.pushNamed(context, '/signup'); }, ) ], diff --git a/examples/todo-list/flutter-todo-list/lib/model.dart b/examples/todo-list/flutter-todo-list/lib/model.dart deleted file mode 100644 index 5c9d8c1d1da..00000000000 --- a/examples/todo-list/flutter-todo-list/lib/model.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:todosupabase/functions/auth.dart'; - -class TodoModel { - String? task; - bool done = false; - String email = AuthSupabase.client.auth.currentUser!.email.toString(); - String? due; - TodoModel({ - required String task, - required String due, - }); -} diff --git a/examples/todo-list/flutter-todo-list/lib/screens/signin.dart b/examples/todo-list/flutter-todo-list/lib/screens/signin.dart index 6490c54f179..db64dc4eab5 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/signin.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/signin.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:todosupabase/constant.dart'; -import 'package:todosupabase/functions/auth.dart'; class SignInPage extends StatefulWidget { const SignInPage({Key? key}) : super(key: key); @@ -14,6 +12,15 @@ class _SignInPageState extends State { final TextEditingController _emailController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); bool loading = false; + Future loginUser({ + required String email, + required String password, + }) async { + final res = await client.auth.signIn(email: email, password: password); + final user = res.data?.user; + return user?.id; + } + @override Widget build(BuildContext context) { return Scaffold( @@ -25,7 +32,10 @@ class _SignInPageState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - SvgPicture.asset('assets/supabase-dark.svg', width: 200), + Image.asset( + 'assets/supabase-logo.png', + height: 40, + ), largeGap, TextFormField( controller: _emailController, @@ -57,7 +67,7 @@ class _SignInPageState extends State { setState(() { loading = true; }); - var value = await AuthSupabase.loginUser( + var value = await loginUser( email: _emailController.text, password: _passwordController.text, ); diff --git a/examples/todo-list/flutter-todo-list/lib/screens/signup.dart b/examples/todo-list/flutter-todo-list/lib/screens/signup.dart index baca47c60d9..3459be2a973 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/signup.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/signup.dart @@ -1,8 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:todosupabase/constant.dart'; -import 'package:todosupabase/functions/auth.dart'; -import 'package:todosupabase/functions/crud.dart'; class SignUpPage extends StatefulWidget { const SignUpPage({Key? key}) : super(key: key); @@ -15,6 +12,30 @@ class _SignUpPageState extends State { final TextEditingController _emailController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); final TextEditingController _nameController = TextEditingController(); + + Future createAccount({ + required String email, + required String password, + }) async { + final res = await client.auth.signUp(email, password); + final error = res.error; + if (error == null) { + return true; + } else { + return false; + } + } + + Future addUser({ + required String name, + required String email, + }) async { + final res = await client.from('Users').insert({ + 'name': name, + 'email': email, + }).execute(); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -26,7 +47,10 @@ class _SignUpPageState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - SvgPicture.asset('assets/supabase-dark.svg', width: 200), + Image.asset( + 'assets/supabase-logo.png', + height: 40, + ), largeGap, TextFormField( controller: _nameController, @@ -54,12 +78,12 @@ class _SignUpPageState extends State { ElevatedButton( child: const Text('Sign Up'), onPressed: () async { - final value = await AuthSupabase.createAccount( + final value = await createAccount( email: _emailController.text, password: _passwordController.text, ); if (value == true) { - CrudSupabase.addUser( + addUser( name: _nameController.text, email: _emailController.text, ); diff --git a/examples/todo-list/flutter-todo-list/lib/screens/todo.dart b/examples/todo-list/flutter-todo-list/lib/screens/todo.dart index 23f0c9a8163..a4fe121cfcf 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/todo.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/todo.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; import 'package:todosupabase/constant.dart'; -import 'package:todosupabase/functions/auth.dart'; -import 'package:todosupabase/functions/crud.dart'; class TodoPage extends StatefulWidget { const TodoPage({Key? key}) : super(key: key); @@ -16,15 +14,90 @@ class _TodoPageState extends State { List todos = []; final TextEditingController _taskEditingController = TextEditingController(); final TextEditingController _dueEditingController = TextEditingController(); + Future logOut(BuildContext context) async { + await client.auth.signOut(); + } + + // GET THE USER DETAILS + Future getUser({required String email}) async { + final res = + await client.from('Users').select().eq('email', email).execute(); + final data = res.data; + return data; + } + + // GETR THE TODOS + Future> getTodo() async { + final res = await client + .from('Todo') + .select('*') + .eq('email', client.auth.currentUser!.email.toString()) + .order('id') + .execute(); + final data = res.data; + return data; + } + + // ADD A Todo IN THE DB + Future addTodo({ + required String task, + required String due, + }) async { + final res = await client.from('Todo').insert({ + 'email': client.auth.currentUser!.email.toString(), + 'task': task, + 'due': due, + 'done': false + }).execute(); + final data = res.data; + return data; + } + + // DELETE A Todo IN THE DB + Future deleteTodo({required String id}) async { + try { + await client.from('Todo').delete().eq('id', id).execute(); + } catch (e) { + print(e); + } + } + + // toggle a Todo IN THE DB + Future taskDone({ + required String id, + required bool value, + }) async { + final res = await client + .from('Todo') + .update({'done': value}) + .eq('id', id) + .execute(); + final data = res.data; + return data; + } + + // Edit a Todo IN THE DB + Future editTask({ + required String id, + required String due, + required String task, + }) async { + final res = await client + .from('Todo') + .update({'due': due, 'task': task}) + .eq('id', id) + .execute(); + final data = res.data; + return data; + } Future initFunc() async { - var nameVal = await CrudSupabase.getUser( - email: AuthSupabase.client.auth.currentUser!.email.toString(), - ); + var nameVal = + await getUser(email: client.auth.currentUser!.email.toString()); setState(() { userName = nameVal[0]['name']; }); - List value = await CrudSupabase.getTodo(); + List value = await getTodo(); for (var element in value) { todos.add(element); } @@ -101,7 +174,7 @@ class _TodoPageState extends State { IconButton( icon: const Icon(Icons.exit_to_app), onPressed: () async { - await AuthSupabase.logOut(context); + await logOut(context); Navigator.pop(context, '/'); }, ), @@ -109,9 +182,8 @@ class _TodoPageState extends State { ), body: loading ? const Center(child: CircularProgressIndicator()) - : SizedBox( - height: double.infinity, - width: double.infinity, + : ConstrainedBox( + constraints: const BoxConstraints(), child: todos.isEmpty ? const Center( child: Text('No Task, add new task'), @@ -127,7 +199,7 @@ class _TodoPageState extends State { taskCtrl: _taskEditingController, dueCtrl: _dueEditingController, onTab: () async { - await CrudSupabase.editTask( + await editTask( id: todos[index]['id'].toString(), due: _dueEditingController.text, task: _taskEditingController.text, @@ -136,8 +208,7 @@ class _TodoPageState extends State { setState(() { loading = true; }); - List value = - await CrudSupabase.getTodo(); + List value = await getTodo(); todos.clear(); setState(() {}); for (var element in value) { @@ -162,11 +233,10 @@ class _TodoPageState extends State { setState(() { loading = true; }); - await CrudSupabase.deleteTodo( + await deleteTodo( id: todos[index]['id'].toString(), ); - List value = - await CrudSupabase.getTodo(); + List value = await getTodo(); todos.clear(); setState(() {}); for (var element in value) { @@ -186,12 +256,11 @@ class _TodoPageState extends State { setState(() { loading = true; }); - await CrudSupabase.taskDone( + await taskDone( id: todos[index]['id'].toString(), value: !todos[index]['done'], ); - List value = - await CrudSupabase.getTodo(); + List value = await getTodo(); todos.clear(); setState(() {}); for (var element in value) { @@ -238,7 +307,7 @@ class _TodoPageState extends State { taskCtrl: _taskEditingController, dueCtrl: _dueEditingController, onTab: () async { - await CrudSupabase.addTodo( + await addTodo( task: _taskEditingController.text, due: _dueEditingController.text, ); @@ -247,7 +316,7 @@ class _TodoPageState extends State { loading = true; }); - List value = await CrudSupabase.getTodo(); + List value = await getTodo(); todos.clear(); setState(() {}); for (var element in value) { diff --git a/examples/todo-list/flutter-todo-list/pubspec.lock b/examples/todo-list/flutter-todo-list/pubspec.lock index 4afdab6011f..0e79993e7c3 100644 --- a/examples/todo-list/flutter-todo-list/pubspec.lock +++ b/examples/todo-list/flutter-todo-list/pubspec.lock @@ -42,7 +42,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.16.0" + version: "1.15.0" crypto: dependency: transitive description: @@ -56,7 +56,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.2.0" ffi: dependency: transitive description: @@ -141,7 +141,7 @@ packages: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.4" + version: "0.6.3" jwt_decode: dependency: transitive description: @@ -149,13 +149,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.3.1" - lints: - dependency: "direct dev" - description: - name: lints - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" matcher: dependency: transitive description: @@ -169,7 +162,7 @@ packages: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.1.3" meta: dependency: transitive description: @@ -190,7 +183,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.0" path_drawing: dependency: transitive description: @@ -307,7 +300,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.8.1" stack_trace: dependency: transitive description: @@ -363,7 +356,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.9" + version: "0.4.8" typed_data: dependency: transitive description: @@ -461,7 +454,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.2" + version: "2.1.1" web_socket_channel: dependency: transitive description: @@ -491,5 +484,5 @@ packages: source: hosted version: "5.3.1" sdks: - dart: ">=2.17.0-206.0.dev <3.0.0" + dart: ">=2.16.1 <3.0.0" flutter: ">=2.10.0" diff --git a/examples/todo-list/flutter-todo-list/pubspec.yaml b/examples/todo-list/flutter-todo-list/pubspec.yaml index 3d4df48cc8d..b8f935d94c3 100644 --- a/examples/todo-list/flutter-todo-list/pubspec.yaml +++ b/examples/todo-list/flutter-todo-list/pubspec.yaml @@ -44,7 +44,7 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - lints: ^2.0.0 + # lints: ^2.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/examples/todo-list/flutter-todo-list/windows/flutter/generated_plugins.cmake b/examples/todo-list/flutter-todo-list/windows/flutter/generated_plugins.cmake index 88b22e5c775..411af46dd72 100644 --- a/examples/todo-list/flutter-todo-list/windows/flutter/generated_plugins.cmake +++ b/examples/todo-list/flutter-todo-list/windows/flutter/generated_plugins.cmake @@ -6,9 +6,6 @@ list(APPEND FLUTTER_PLUGIN_LIST url_launcher_windows ) -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -17,8 +14,3 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) From 40adf22597330044910cec6470c97e48353ee526 Mon Sep 17 00:00:00 2001 From: Aniket Singh <31aniket.singh@gmail.com> Date: Sun, 19 Jun 2022 16:20:32 +0530 Subject: [PATCH 25/57] removed user database and changed todos db same as nextjs todo example --- .../flutter-todo-list/lib/screens/signup.dart | 14 --------- .../flutter-todo-list/lib/screens/todo.dart | 30 +++++-------------- .../todo-list/flutter-todo-list/pubspec.yaml | 2 +- 3 files changed, 9 insertions(+), 37 deletions(-) diff --git a/examples/todo-list/flutter-todo-list/lib/screens/signup.dart b/examples/todo-list/flutter-todo-list/lib/screens/signup.dart index 3459be2a973..0ff98c454e4 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/signup.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/signup.dart @@ -26,16 +26,6 @@ class _SignUpPageState extends State { } } - Future addUser({ - required String name, - required String email, - }) async { - final res = await client.from('Users').insert({ - 'name': name, - 'email': email, - }).execute(); - } - @override Widget build(BuildContext context) { return Scaffold( @@ -83,10 +73,6 @@ class _SignUpPageState extends State { password: _passwordController.text, ); if (value == true) { - addUser( - name: _nameController.text, - email: _emailController.text, - ); Navigator.pushReplacementNamed(context, '/signin'); } }, diff --git a/examples/todo-list/flutter-todo-list/lib/screens/todo.dart b/examples/todo-list/flutter-todo-list/lib/screens/todo.dart index a4fe121cfcf..253f20fa504 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/todo.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/todo.dart @@ -9,7 +9,6 @@ class TodoPage extends StatefulWidget { } class _TodoPageState extends State { - String userName = ''; bool loading = true; List todos = []; final TextEditingController _taskEditingController = TextEditingController(); @@ -18,20 +17,12 @@ class _TodoPageState extends State { await client.auth.signOut(); } - // GET THE USER DETAILS - Future getUser({required String email}) async { - final res = - await client.from('Users').select().eq('email', email).execute(); - final data = res.data; - return data; - } - // GETR THE TODOS Future> getTodo() async { final res = await client - .from('Todo') + .from('todos') .select('*') - .eq('email', client.auth.currentUser!.email.toString()) + .eq('user_id', client.auth.currentUser!.id) .order('id') .execute(); final data = res.data; @@ -43,8 +34,8 @@ class _TodoPageState extends State { required String task, required String due, }) async { - final res = await client.from('Todo').insert({ - 'email': client.auth.currentUser!.email.toString(), + final res = await client.from('todos').insert({ + 'user_id': client.auth.currentUser!.id, 'task': task, 'due': due, 'done': false @@ -56,7 +47,7 @@ class _TodoPageState extends State { // DELETE A Todo IN THE DB Future deleteTodo({required String id}) async { try { - await client.from('Todo').delete().eq('id', id).execute(); + await client.from('todos').delete().eq('id', id).execute(); } catch (e) { print(e); } @@ -68,7 +59,7 @@ class _TodoPageState extends State { required bool value, }) async { final res = await client - .from('Todo') + .from('todos') .update({'done': value}) .eq('id', id) .execute(); @@ -83,7 +74,7 @@ class _TodoPageState extends State { required String task, }) async { final res = await client - .from('Todo') + .from('todos') .update({'due': due, 'task': task}) .eq('id', id) .execute(); @@ -92,11 +83,6 @@ class _TodoPageState extends State { } Future initFunc() async { - var nameVal = - await getUser(email: client.auth.currentUser!.email.toString()); - setState(() { - userName = nameVal[0]['name']; - }); List value = await getTodo(); for (var element in value) { todos.add(element); @@ -169,7 +155,7 @@ class _TodoPageState extends State { appBar: AppBar( backgroundColor: Colors.black, automaticallyImplyLeading: false, - title: Text('Hey $userName, add todo'), + title: Text('Supabase Todo'), actions: [ IconButton( icon: const Icon(Icons.exit_to_app), diff --git a/examples/todo-list/flutter-todo-list/pubspec.yaml b/examples/todo-list/flutter-todo-list/pubspec.yaml index b8f935d94c3..3d4df48cc8d 100644 --- a/examples/todo-list/flutter-todo-list/pubspec.yaml +++ b/examples/todo-list/flutter-todo-list/pubspec.yaml @@ -44,7 +44,7 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - # lints: ^2.0.0 + lints: ^2.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec From 49ce7ee6b45ca2161ecc1d2c00181ff584fcb1ef Mon Sep 17 00:00:00 2001 From: Aniket Singh <31aniket.singh@gmail.com> Date: Sun, 19 Jun 2022 16:27:22 +0530 Subject: [PATCH 26/57] removed user database and changed todos db same as nextjs todo example --- .../flutter-todo-list/lib/screens/todo.dart | 24 ++----------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/examples/todo-list/flutter-todo-list/lib/screens/todo.dart b/examples/todo-list/flutter-todo-list/lib/screens/todo.dart index 253f20fa504..91159aaea9b 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/todo.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/todo.dart @@ -12,7 +12,6 @@ class _TodoPageState extends State { bool loading = true; List todos = []; final TextEditingController _taskEditingController = TextEditingController(); - final TextEditingController _dueEditingController = TextEditingController(); Future logOut(BuildContext context) async { await client.auth.signOut(); } @@ -32,13 +31,10 @@ class _TodoPageState extends State { // ADD A Todo IN THE DB Future addTodo({ required String task, - required String due, }) async { final res = await client.from('todos').insert({ 'user_id': client.auth.currentUser!.id, 'task': task, - 'due': due, - 'done': false }).execute(); final data = res.data; return data; @@ -70,12 +66,11 @@ class _TodoPageState extends State { // Edit a Todo IN THE DB Future editTask({ required String id, - required String due, required String task, }) async { final res = await client .from('todos') - .update({'due': due, 'task': task}) + .update({'task': task}) .eq('id', id) .execute(); final data = res.data; @@ -102,14 +97,12 @@ class _TodoPageState extends State { void dispose() { super.dispose(); _taskEditingController.dispose(); - _dueEditingController.dispose(); } Future displayTextInputDialog({ required BuildContext context, void Function()? onTab, required TextEditingController taskCtrl, - required TextEditingController dueCtrl, }) async { return showDialog( context: context, @@ -124,12 +117,6 @@ class _TodoPageState extends State { decoration: const InputDecoration(hintText: 'Add title of todo'), ), - TextField( - controller: dueCtrl, - keyboardType: TextInputType.datetime, - decoration: - const InputDecoration(hintText: 'Add Due Date in DD/MM'), - ), smallGap, MaterialButton( onPressed: onTab, @@ -183,11 +170,9 @@ class _TodoPageState extends State { displayTextInputDialog( context: context, taskCtrl: _taskEditingController, - dueCtrl: _dueEditingController, onTab: () async { await editTask( id: todos[index]['id'].toString(), - due: _dueEditingController.text, task: _taskEditingController.text, ); Navigator.pop(context); @@ -210,10 +195,7 @@ class _TodoPageState extends State { todos[index]['task'], style: const TextStyle(color: Colors.white), ), - subtitle: Text( - todos[index]['due'], - style: const TextStyle(color: Colors.white), - ), + leading: GestureDetector( onTap: () async { setState(() { @@ -291,11 +273,9 @@ class _TodoPageState extends State { displayTextInputDialog( context: context, taskCtrl: _taskEditingController, - dueCtrl: _dueEditingController, onTab: () async { await addTodo( task: _taskEditingController.text, - due: _dueEditingController.text, ); Navigator.pop(context); setState(() { From e20b02a3804dc9f9900ac459b0b068d7221a672f Mon Sep 17 00:00:00 2001 From: AliZulfaqar Date: Sun, 19 Jun 2022 21:44:27 +0800 Subject: [PATCH 27/57] Update local-development.mdx - add missing t for platform --- web/docs/guides/local-development.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/docs/guides/local-development.mdx b/web/docs/guides/local-development.mdx index fc95160cf38..c1a27241444 100644 --- a/web/docs/guides/local-development.mdx +++ b/web/docs/guides/local-development.mdx @@ -244,7 +244,7 @@ supabase functions deploy ## Limitations -The local development environment is not as feature-complete as the Plaform. Here are some of the differences: +The local development environment is not as feature-complete as the Platform. Here are some of the differences: - The Storage interface is coming soon. - The Functions interface is coming soon. From 2bbf9bf49ca35f9b69e9dea00e25aa8045767393 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Mon, 20 Jun 2022 16:30:02 +1000 Subject: [PATCH 28/57] fix: sql create from template --- studio/components/to-be-cleaned/SqlEditor/TabWelcome.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/to-be-cleaned/SqlEditor/TabWelcome.js b/studio/components/to-be-cleaned/SqlEditor/TabWelcome.js index dbdf030e296..2d36ad2ec4b 100644 --- a/studio/components/to-be-cleaned/SqlEditor/TabWelcome.js +++ b/studio/components/to-be-cleaned/SqlEditor/TabWelcome.js @@ -62,7 +62,7 @@ const TabWelcome = observer(() => { description={x.description} sql={x.sql} onClick={(sql, title) => { - handleNewQuery(sql, title) + handleNewQuery({ sql, name: title }) Telemetry.sendEvent('quickstart', 'quickstart_clicked', x.title) }} /> From a308120cf8bed884c991e028491000ff6fb8eb8c Mon Sep 17 00:00:00 2001 From: Aniket Singh <63359563+Anikets08@users.noreply.github.com> Date: Mon, 20 Jun 2022 13:55:12 +0530 Subject: [PATCH 29/57] Update examples/todo-list/flutter-todo-list/lib/screens/todo.dart Co-authored-by: Tyler <18113850+dshukertjr@users.noreply.github.com> --- examples/todo-list/flutter-todo-list/lib/screens/todo.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/todo-list/flutter-todo-list/lib/screens/todo.dart b/examples/todo-list/flutter-todo-list/lib/screens/todo.dart index 91159aaea9b..fc21fa568f2 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/todo.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/todo.dart @@ -181,7 +181,6 @@ class _TodoPageState extends State { }); List value = await getTodo(); todos.clear(); - setState(() {}); for (var element in value) { todos.add(element); } From 4bb4f6eee1adb2d6bd88c078ae13030297dcc588 Mon Sep 17 00:00:00 2001 From: Aniket Singh <31aniket.singh@gmail.com> Date: Mon, 20 Jun 2022 14:03:03 +0530 Subject: [PATCH 30/57] removed unwanted setState() --- examples/todo-list/flutter-todo-list/lib/screens/todo.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/todo-list/flutter-todo-list/lib/screens/todo.dart b/examples/todo-list/flutter-todo-list/lib/screens/todo.dart index fc21fa568f2..1d1c0f8cf9e 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/todo.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/todo.dart @@ -205,7 +205,6 @@ class _TodoPageState extends State { ); List value = await getTodo(); todos.clear(); - setState(() {}); for (var element in value) { todos.add(element); } @@ -229,7 +228,6 @@ class _TodoPageState extends State { ); List value = await getTodo(); todos.clear(); - setState(() {}); for (var element in value) { todos.add(element); } @@ -283,7 +281,6 @@ class _TodoPageState extends State { List value = await getTodo(); todos.clear(); - setState(() {}); for (var element in value) { todos.add(element); } From 45bc4c2af51eb6208971918371ccef91f54c8f5e Mon Sep 17 00:00:00 2001 From: Aniket Singh <31aniket.singh@gmail.com> Date: Mon, 20 Jun 2022 14:05:59 +0530 Subject: [PATCH 31/57] onTab changed to onTap --- .../todo-list/flutter-todo-list/lib/screens/todo.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/todo-list/flutter-todo-list/lib/screens/todo.dart b/examples/todo-list/flutter-todo-list/lib/screens/todo.dart index 1d1c0f8cf9e..7c88411a7d8 100644 --- a/examples/todo-list/flutter-todo-list/lib/screens/todo.dart +++ b/examples/todo-list/flutter-todo-list/lib/screens/todo.dart @@ -101,7 +101,7 @@ class _TodoPageState extends State { Future displayTextInputDialog({ required BuildContext context, - void Function()? onTab, + void Function()? onTap, required TextEditingController taskCtrl, }) async { return showDialog( @@ -119,7 +119,7 @@ class _TodoPageState extends State { ), smallGap, MaterialButton( - onPressed: onTab, + onPressed: onTap, child: const Text( 'Add Task', style: TextStyle( @@ -170,7 +170,7 @@ class _TodoPageState extends State { displayTextInputDialog( context: context, taskCtrl: _taskEditingController, - onTab: () async { + onTap: () async { await editTask( id: todos[index]['id'].toString(), task: _taskEditingController.text, @@ -270,7 +270,7 @@ class _TodoPageState extends State { displayTextInputDialog( context: context, taskCtrl: _taskEditingController, - onTab: () async { + onTap: () async { await addTodo( task: _taskEditingController.text, ); From 969d5a62ea4c9d205308a132be96c37ca9f602ba Mon Sep 17 00:00:00 2001 From: Ollie Halliday <22655069+Hallidayo@users.noreply.github.com> Date: Mon, 20 Jun 2022 14:14:45 +0100 Subject: [PATCH 32/57] added sort function for the examples --- studio/pages/project/[ref]/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/pages/project/[ref]/index.tsx b/studio/pages/project/[ref]/index.tsx index eba3af89bf4..f71f809245d 100644 --- a/studio/pages/project/[ref]/index.tsx +++ b/studio/pages/project/[ref]/index.tsx @@ -38,7 +38,7 @@ const Home: NextPageWithLayout = () => { Example projects
- {EXAMPLE_PROJECTS.map((project) => ( + {EXAMPLE_PROJECTS.sort((a, b) => a.title.localeCompare(b.title)).map((project) => ( ))}
From b5f7d4c9a27ee98bb69d40be4508f0175eacbf97 Mon Sep 17 00:00:00 2001 From: Ollie Halliday <22655069+Hallidayo@users.noreply.github.com> Date: Mon, 20 Jun 2022 14:18:23 +0100 Subject: [PATCH 33/57] renamed some of the libaries/frameworks --- studio/components/interfaces/Home/Home.constants.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/studio/components/interfaces/Home/Home.constants.ts b/studio/components/interfaces/Home/Home.constants.ts index d19e89231bb..68c22b13343 100644 --- a/studio/components/interfaces/Home/Home.constants.ts +++ b/studio/components/interfaces/Home/Home.constants.ts @@ -55,7 +55,7 @@ export const EXAMPLE_PROJECTS = [ }, { framework: 'React', - title: 'React.js realtime chat app', + title: 'React realtime chat app', description: 'Example app of real-time chat using supabase realtime api', url: 'https://github.com/shwosner/realtime-chat-supabase-react', }, @@ -73,7 +73,7 @@ export const EXAMPLE_PROJECTS = [ }, { framework: 'NextJS', - title: 'NextJS todo list app', + title: 'Next.js todo list app', description: 'NextJS todo list example', url: 'https://github.com/supabase/supabase/tree/master/examples/todo-list/nextjs-todo-list', }, From 5c3314866a5a7c2d766cc17548ef780e28ce5950 Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton <70828596+Isaiah-Hamilton@users.noreply.github.com> Date: Mon, 20 Jun 2022 22:21:00 -0400 Subject: [PATCH 34/57] chore: update company page --- apps/www/data/Press.ts | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/apps/www/data/Press.ts b/apps/www/data/Press.ts index 8f68720299b..598c3e712d6 100644 --- a/apps/www/data/Press.ts +++ b/apps/www/data/Press.ts @@ -24,18 +24,6 @@ const data: PressItem[] = [ title: 'Our investment in Supabase', publisher: 'Medium', }, - { - type: 'article', - href: 'https://techcrunch.com/2021/09/09/supabase-raises-30m-for-its-open-source-insta-backend/', - title: 'Open source backend-as-a-service startup Supabase raises $30M', - publisher: 'TechCrunch', - }, - { - type: 'article', - href: 'https://techcrunch.com/2020/12/15/supabase-raises-6m-for-its-open-source-firebase-alternative/', - title: 'Supabase raises $6M for its open-source Firebase alternative', - publisher: 'TechCrunch', - }, { type: 'podcast', href: 'https://podcasts.apple.com/us/podcast/73-elixir-at-supabase-with-paul-copplestone/id1516100616?i=1000542087899', @@ -44,9 +32,9 @@ const data: PressItem[] = [ }, { type: 'podcast', - href: 'https://changelog.com/podcast/476', - title: 'Supabase is all in on Postgres', - publisher: 'changelog', + href: 'https://podcasts.apple.com/us/podcast/73-elixir-at-supabase-with-paul-copplestone/id1516100616?i=1000542087899', + title: 'Ant Wilson on Supabase ', + publisher: 'Software Engineering Radio', }, { type: 'podcast', @@ -60,6 +48,12 @@ const data: PressItem[] = [ title: 'Open-source is winning over developers and investors', publisher: 'Stackoverflow', }, + { + type: 'podcast', + href: 'https://changelog.com/podcast/476', + title: 'Supabase is all in on Postgres', + publisher: 'changelog', + }, { type: 'podcast', href: 'https://fsjam.org/episodes/episode-33-supabase-with-paul-copplestone', From bfa5a986e3ad728fd77c4e89b889515b1e8ab6e5 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Mon, 20 Jun 2022 16:01:23 +0800 Subject: [PATCH 35/57] Make error toasts not auto dismiss by default --- .../Authentication/Policies/PolicyEditorModal/index.tsx | 4 ++++ .../SidePanelEditor/RowEditor/JsonEditor/JsonEditor.tsx | 4 ++-- .../TableGridEditor/SidePanelEditor/RowEditor/RowEditor.tsx | 2 ++ .../TableEditor/SpreadsheetImport/SpreadsheetImport.tsx | 6 ++++-- .../SidePanelEditor/TableEditor/TableEditor.tsx | 2 +- studio/hooks/misc/useStore.tsx | 2 +- 6 files changed, 14 insertions(+), 6 deletions(-) diff --git a/studio/components/interfaces/Authentication/Policies/PolicyEditorModal/index.tsx b/studio/components/interfaces/Authentication/Policies/PolicyEditorModal/index.tsx index 1f63042e15c..5503d234ac4 100644 --- a/studio/components/interfaces/Authentication/Policies/PolicyEditorModal/index.tsx +++ b/studio/components/interfaces/Authentication/Policies/PolicyEditorModal/index.tsx @@ -120,18 +120,21 @@ const PolicyEditorModal: FC = ({ return ui.setNotification({ category: 'error', message: 'You will need to allow at one operation in your policy', + duration: 4000, }) } if (['SELECT', 'DELETE'].includes(command) && !definition) { return ui.setNotification({ category: 'error', message: 'Did you forget to provide a USING expression for your policy?', + duration: 4000, }) } if (command === 'INSERT' && !check) { return ui.setNotification({ category: 'error', message: 'Did you forget to provide a WITH CHECK expression for your policy?', + duration: 4000, }) } if (command === 'UPDATE' && !definition && !check) { @@ -139,6 +142,7 @@ const PolicyEditorModal: FC = ({ category: 'error', message: 'You will need to provide either a USING, or WITH CHECK expression, or both for your policy', + duration: 4000, }) } const policySQLStatement = createSQLPolicy(policyFormFields, selectedPolicyToEdit) diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/JsonEditor/JsonEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/JsonEditor/JsonEditor.tsx index 2298bc6cddb..67ce557846c 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/JsonEditor/JsonEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/JsonEditor/JsonEditor.tsx @@ -33,8 +33,8 @@ const JsonEdit: FC = ({ column, jsonString, visible, closePanel, } catch (error: any) { const message = error.message ? `Error: ${error.message}` - : 'Hmm, invalid JSON seems to have an invalid structure.' - ui.setNotification({ category: 'error', message }) + : 'JSON seems to have an invalid structure.' + ui.setNotification({ category: 'error', message, duration: 4000 }) } finally { resolve() } diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.tsx index 40dc8b0ac7e..02d985c796d 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.tsx @@ -82,6 +82,7 @@ const RowEditor: FC = ({ ui.setNotification({ category: 'error', message: `Please enter a value in the ${row.name} field first`, + duration: 4000, }) } const foreignKey = row.foreignKey @@ -108,6 +109,7 @@ const RowEditor: FC = ({ return ui.setNotification({ category: 'error', message: `Unable to find the corresponding row in ${foreignKey.target_table_schema}.${foreignKey.target_table_name} where ${foreignKey.target_column_name} equals ${row.value}`, + duration: 4000, }) } setReferenceRow({ loading: false, foreignKey, row: res[0] }) diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.tsx index ea9618c96f1..2b80a7340d2 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.tsx @@ -74,6 +74,7 @@ const SpreadsheetImport: FC = ({ error: errors, category: 'error', message: `Some issues have been detected on ${errors.length} rows. More details below the content preview.`, + duration: 4000, }) } setErrors(errors) @@ -98,6 +99,7 @@ const SpreadsheetImport: FC = ({ error: errors, category: 'error', message: `Some issues have been detected on ${errors.length} rows. More details below the content preview.`, + duration: 4000, }) } setErrors(errors) @@ -160,7 +162,7 @@ const SpreadsheetImport: FC = ({ {spreadsheetData.headers.length > 0 && ( -
+
Content Preview @@ -188,7 +190,7 @@ const SpreadsheetImport: FC = ({
onSelectExpandError(key)} /> diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx index 6fcba4fe396..466aa6d0ccc 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx @@ -99,7 +99,7 @@ const TableEditor: FC = ({ if (tableFields) { const errors: any = validateFields(tableFields) if (errors.columns) { - ui.setNotification({ category: 'error', message: errors.columns }) + ui.setNotification({ category: 'error', message: errors.columns, duration: 4000 }) } setErrors(errors) diff --git a/studio/hooks/misc/useStore.tsx b/studio/hooks/misc/useStore.tsx index 15f553cb98b..b267cb010a4 100644 --- a/studio/hooks/misc/useStore.tsx +++ b/studio/hooks/misc/useStore.tsx @@ -58,7 +58,7 @@ export const StoreProvider: FC = ({ children, rootStore }) => { return toast.success(message, { id, duration: toastDuration }) case 'error': console.error('Error:', { error, message }) - return toast.error(message, { id, duration: toastDuration }) + return toast.error(message, { id, duration: duration || Infinity }) case 'loading': if (progress) { return toast.loading( From f74d40cb87266f0a6960e5eb4219eee7f3e1146a Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 15 Jun 2022 15:38:55 +0800 Subject: [PATCH 36/57] Leave numerical values as string in RowEditor when formatting query to work around js integer range limits --- .../SidePanelEditor/RowEditor/RowEditor.utils.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.utils.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.utils.ts index 81826dcbf9b..fb93bd3f0e6 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.utils.ts +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.utils.ts @@ -283,8 +283,6 @@ export const generateRowObjectFromFields = ( rowObject[field.name] = tryParseJson(value) } } - } else if (NUMERICAL_TYPES.includes(field.format) && value) { - rowObject[field.name] = Number(value) } else if (field.format === 'bool' && value) { rowObject[field.name] = value === 'true' } else if (DATETIME_TYPES.includes(field.format)) { From 6ae9a94000ac260c3773a52bf6edc42d8fada834 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 15 Jun 2022 16:14:29 +0800 Subject: [PATCH 37/57] Ensure that grid editor updates entire row based on update response so that dynamic columns will be reflected --- .../components/grid/components/grid/Grid.tsx | 94 +++++----- .../grid/services/row/SqlRowService.ts | 170 +++++++++--------- studio/components/grid/services/row/index.tsx | 24 +-- 3 files changed, 139 insertions(+), 149 deletions(-) diff --git a/studio/components/grid/components/grid/Grid.tsx b/studio/components/grid/components/grid/Grid.tsx index 43f2757aa2b..e49d199a13a 100644 --- a/studio/components/grid/components/grid/Grid.tsx +++ b/studio/components/grid/components/grid/Grid.tsx @@ -1,17 +1,14 @@ -import * as React from 'react'; -import { memo } from 'react-tracked'; -import DataGrid, { - DataGridHandle, - RowsChangeData, -} from '@supabase/react-data-grid'; -import { IconLoader } from '@supabase/ui'; -import { GridProps, SupaRow } from '../../types'; -import { useDispatch, useTrackedState } from '../../store'; -import RowRenderer from './RowRenderer'; -import AwesomeDebouncePromise from 'awesome-debounce-promise'; +import * as React from 'react' +import { memo } from 'react-tracked' +import DataGrid, { DataGridHandle, RowsChangeData } from '@supabase/react-data-grid' +import { IconLoader } from '@supabase/ui' +import { GridProps, SupaRow } from '../../types' +import { useDispatch, useTrackedState } from '../../store' +import RowRenderer from './RowRenderer' +import AwesomeDebouncePromise from 'awesome-debounce-promise' function rowKeyGetter(row: SupaRow) { - return row.idx; + return row.idx } export const Grid = memo( @@ -20,34 +17,36 @@ export const Grid = memo( { width, height, containerClass, gridClass, rowClass }, ref: React.Ref | undefined ) => { - const dispatch = useDispatch(); - const state = useTrackedState(); + const dispatch = useDispatch() + const state = useTrackedState() // workaround to force state tracking on state.gridColumns - const columnHeaders = state.gridColumns.map( - (x) => `${x.key}_${x.frozen}` - ); - const { gridColumns, rows, onError: onErrorFunc } = state; + const columnHeaders = state.gridColumns.map((x) => `${x.key}_${x.frozen}`) + const { gridColumns, rows, onError: onErrorFunc } = state function onColumnResize(index: number, width: number) { - updateColumnResizeDebounced(index, width, dispatch); + updateColumnResizeDebounced(index, width, dispatch) } - function onRowsChange( - rows: SupaRow[], - data: RowsChangeData - ) { - const rowData = rows[data.indexes[0]]; - const originRowData = state.rows.find((x) => x.idx == rowData.idx); - const changedColumn = Object.keys(rowData).find((name) => rowData[name] !== originRowData![name]); + async function onRowsChange(rows: SupaRow[], data: RowsChangeData) { + const rowData = rows[data.indexes[0]] + const originRowData = state.rows.find((x) => x.idx == rowData.idx) + const changedColumn = Object.keys(rowData).find( + (name) => rowData[name] !== originRowData![name] + ) if (changedColumn) { - const { error } = state.rowService!.update(rowData, changedColumn); - if (error) { - if (onErrorFunc) onErrorFunc(error); - } else { + const { error } = state.rowService!.update(rowData, changedColumn, (payload) => { dispatch({ - type: 'SET_ROWS', - payload: { rows }, - }); + type: 'EDIT_ROW', + payload, + }) + }) + if (error) { + if (onErrorFunc) onErrorFunc(error) + } else { + // dispatch({ + // type: 'SET_ROWS', + // payload: { rows }, + // }) } } } @@ -56,14 +55,14 @@ export const Grid = memo( dispatch({ type: 'SELECTED_ROWS_CHANGE', payload: { selectedRows }, - }); + }) } function onSelectedCellChange(position: { idx: number; rowIdx: number }) { dispatch({ type: 'SELECTED_CELL_CHANGE', payload: { position }, - }); + }) } if (!columnHeaders || columnHeaders.length == 0) { @@ -73,13 +72,13 @@ export const Grid = memo( style={{ width: width || '100%', height: height || '50vh' }} >
-
+
-
Loading...
+
Loading...
- ); + ) } return (
- ); + ) } ) -); +) -const updateColumnResize = ( - index: number, - width: number, - dispatch: (value: unknown) => void -) => { +const updateColumnResize = (index: number, width: number, dispatch: (value: unknown) => void) => { dispatch({ type: 'UPDATE_COLUMN_SIZE', payload: { index, width: Math.round(width) }, - }); -}; -const updateColumnResizeDebounced = AwesomeDebouncePromise( - updateColumnResize, - 500 -); + }) +} +const updateColumnResizeDebounced = AwesomeDebouncePromise(updateColumnResize, 500) diff --git a/studio/components/grid/services/row/SqlRowService.ts b/studio/components/grid/services/row/SqlRowService.ts index 057d56ad3d0..9605133ef2f 100644 --- a/studio/components/grid/services/row/SqlRowService.ts +++ b/studio/components/grid/services/row/SqlRowService.ts @@ -1,144 +1,140 @@ -import { IRowService } from '.'; -import { Filter, ServiceError, Sort, SupaRow, SupaTable } from '../../types'; -import { ERROR_PRIMARY_KEY_NOTFOUND, SupabaseGridQueue } from '../../constants'; -import Query from '../../query'; -import { isNumericalColumn } from '../../utils'; +import { IRowService } from '.' +import { Filter, ServiceError, Sort, SupaRow, SupaTable } from '../../types' +import { ERROR_PRIMARY_KEY_NOTFOUND, SupabaseGridQueue } from '../../constants' +import Query from '../../query' +import { isNumericalColumn } from '../../utils' +import { useDispatch } from 'components/grid/store' export class SqlRowService implements IRowService { - protected query = new Query(); + protected query = new Query() + protected dispatch = useDispatch() constructor( protected table: SupaTable, - protected onSqlQuery: ( - query: string - ) => Promise<{ data?: any; error?: any }>, + protected onSqlQuery: (query: string) => Promise<{ data?: any; error?: any }>, protected onError: (error: any) => void ) {} async count(filters: Filter[]) { - let queryChains = this.query - .from(this.table.name, this.table.schema ?? undefined) - .count(); + let queryChains = this.query.from(this.table.name, this.table.schema ?? undefined).count() filters .filter((x) => x.value && x.value != '') .forEach((x) => { - const value = this.formatFilterValue(x); - queryChains = queryChains.filter(x.column, x.operator, value); - }); + const value = this.formatFilterValue(x) + queryChains = queryChains.filter(x.column, x.operator, value) + }) - const query = queryChains.toSql(); - const { data, error } = await this.onSqlQuery(query); + const query = queryChains.toSql() + const { data, error } = await this.onSqlQuery(query) if (error) { - return { error }; + return { error } } else { if (data?.length == 1) { - return { data: data[0].count }; + return { data: data[0].count } } else { - return { error: { message: 'fetch rows count failed' } }; + return { error: { message: 'fetch rows count failed' } } } } } async create(row: SupaRow) { - console.log('create: ', row); - return { error: { message: 'not implemented' } }; + console.log('create: ', row) + return { error: { message: 'not implemented' } } } delete(rows: SupaRow[]) { - const { primaryKeys, error } = this.getPrimaryKeys(); - if (error) return { error }; + const { primaryKeys, error } = this.getPrimaryKeys() + if (error) return { error } - let queryChains = this.query - .from(this.table.name, this.table.schema ?? undefined) - .delete(); + let queryChains = this.query.from(this.table.name, this.table.schema ?? undefined).delete() primaryKeys!.forEach((key) => { - const primaryKeyValues = rows.map((x) => x[key]); - queryChains = queryChains.filter(key, 'in', primaryKeyValues); - }); + const primaryKeyValues = rows.map((x) => x[key]) + queryChains = queryChains.filter(key, 'in', primaryKeyValues) + }) - const query = queryChains.toSql(); + const query = queryChains.toSql() SupabaseGridQueue.add(async () => { - const { error } = await this.onSqlQuery(query); - if (error) throw error; + const { error } = await this.onSqlQuery(query) + if (error) throw error }).catch((error) => { - this.onError(error); - }); + this.onError(error) + }) - return {}; + return {} } - async fetchPage( - page: number, - rowsPerPage: number, - filters: Filter[], - sorts: Sort[] - ) { - const pageFromZero = page > 0 ? page - 1 : page; - const from = pageFromZero * rowsPerPage; - const to = (pageFromZero + 1) * rowsPerPage - 1; - let queryChains = this.query - .from(this.table.name, this.table.schema ?? undefined) - .select(); + async fetchPage(page: number, rowsPerPage: number, filters: Filter[], sorts: Sort[]) { + const pageFromZero = page > 0 ? page - 1 : page + const from = pageFromZero * rowsPerPage + const to = (pageFromZero + 1) * rowsPerPage - 1 + let queryChains = this.query.from(this.table.name, this.table.schema ?? undefined).select() filters .filter((x) => x.value && x.value != '') .forEach((x) => { - const value = this.formatFilterValue(x); - queryChains = queryChains.filter(x.column, x.operator, value); - }); + const value = this.formatFilterValue(x) + queryChains = queryChains.filter(x.column, x.operator, value) + }) sorts.forEach((x) => { - queryChains = queryChains.order(x.column, x.ascending, x.nullsFirst); - }); + queryChains = queryChains.order(x.column, x.ascending, x.nullsFirst) + }) - const query = queryChains.range(from, to).toSql(); - const { data, error } = await this.onSqlQuery(query); + const query = queryChains.range(from, to).toSql() + const { data, error } = await this.onSqlQuery(query) if (error) { - this.onError(error); - return { data: { rows: [] } }; + this.onError(error) + return { data: { rows: [] } } } else { const rows = data?.map((x: any, index: number) => { - return { idx: index, ...x } as SupaRow; - }); - return { data: { rows } }; + return { idx: index, ...x } as SupaRow + }) + return { data: { rows } } } } - update(row: SupaRow, changedColumn?: string) { - const { primaryKeys, error } = this.getPrimaryKeys(); + update(row: SupaRow, changedColumn?: string, onRowUpdate?: (value: any) => void) { + const { primaryKeys, error } = this.getPrimaryKeys() if (error) { - return { error }; + return { error } } - const { idx, ...value } = row; - const matchValues: any = {}; + const { idx, ...value } = row + const matchValues: any = {} primaryKeys!.forEach((key) => { - matchValues[key] = row[key]; + matchValues[key] = row[key] // fix: https://github.com/supabase/grid/issues/94 // remove primary key from updated value object - delete value[key]; - }); + delete value[key] + }) const query = this.query .from(this.table.name, this.table.schema ?? undefined) - .update(changedColumn ? { - [changedColumn]: value[changedColumn] - }: value) + .update( + changedColumn + ? { + [changedColumn]: value[changedColumn], + } + : value, + { returning: true } + ) .match(matchValues) - .toSql(); - SupabaseGridQueue.add(async () => { - const { error } = await this.onSqlQuery(query); - if (error) throw error; - }).catch((error) => { - this.onError(error); - }); + .toSql() - return {}; + SupabaseGridQueue.add(async () => { + const { data, error } = await this.onSqlQuery(query) + if (error) throw error + if (onRowUpdate) onRowUpdate({ row: data[0], idx }) + }).catch((error) => { + this.onError(error) + }) + + return {} } getPrimaryKeys(): { primaryKeys?: string[]; error?: ServiceError } { - const pkColumns = this.table.columns.filter((x) => x.isPrimaryKey); + const pkColumns = this.table.columns.filter((x) => x.isPrimaryKey) if (!pkColumns || pkColumns.length == 0) { - return { error: { message: ERROR_PRIMARY_KEY_NOTFOUND } }; + return { error: { message: ERROR_PRIMARY_KEY_NOTFOUND } } } - return { primaryKeys: pkColumns.map((x) => x.name) }; + return { primaryKeys: pkColumns.map((x) => x.name) } } /** @@ -146,12 +142,12 @@ export class SqlRowService implements IRowService { * which validate input value base on the column type */ formatFilterValue(filter: Filter) { - const column = this.table.columns.find((x) => x.name == filter.column); + const column = this.table.columns.find((x) => x.name == filter.column) if (column && isNumericalColumn(column.format)) { - const numberValue = Number(filter.value); - if (Number.isNaN(numberValue)) return filter.value; - else return Number(filter.value); + const numberValue = Number(filter.value) + if (Number.isNaN(numberValue)) return filter.value + else return Number(filter.value) } - return filter.value; + return filter.value } } diff --git a/studio/components/grid/services/row/index.tsx b/studio/components/grid/services/row/index.tsx index 36bbb934ff3..666f4f1a728 100644 --- a/studio/components/grid/services/row/index.tsx +++ b/studio/components/grid/services/row/index.tsx @@ -1,11 +1,9 @@ -import { Filter, ServiceError, Sort, SupaRow } from '../../types'; +import { Filter, ServiceError, Sort, SupaRow } from '../../types' export interface IRowService { - count: ( - filters: Filter[] - ) => Promise<{ data?: number; error?: ServiceError }>; + count: (filters: Filter[]) => Promise<{ data?: number; error?: ServiceError }> - create: (row: SupaRow) => Promise<{ data?: SupaRow; error?: ServiceError }>; + create: (row: SupaRow) => Promise<{ data?: SupaRow; error?: ServiceError }> fetchPage: ( page: number, @@ -13,9 +11,9 @@ export interface IRowService { filters: Filter[], sorts: Sort[] ) => Promise<{ - data?: { rows: SupaRow[] }; - error?: ServiceError; - }>; + data?: { rows: SupaRow[] } + error?: ServiceError + }> /** * TODO: should return a promise. @@ -25,9 +23,13 @@ export interface IRowService { * Right now, we remove rows immediately, so it's confused when delete operation fails * and rows are already removed from the grid */ - delete: (rows: SupaRow[]) => { error?: ServiceError }; + delete: (rows: SupaRow[]) => { error?: ServiceError } - update: (row: SupaRow, changedColumn?: string) => { error?: ServiceError }; + update: ( + row: SupaRow, + changedColumn?: string, + onRowUpdate?: (value: any) => void + ) => { row?: SupaRow; error?: ServiceError } } -export * from './SqlRowService'; +export * from './SqlRowService' From 069379d053de8744f5c762abfab94542912267f3 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Mon, 20 Jun 2022 14:38:31 +0800 Subject: [PATCH 38/57] Add option to toggle realtime for a table in table editor side panel --- .../Publications/PublicationsTableItem.tsx | 6 +- .../SidePanelEditor/SidePanelEditor.tsx | 20 ++++-- .../TableEditor/TableEditor.tsx | 40 +++++++++--- .../TableEditor/TableEditor.types.ts | 1 + .../TableEditor/TableEditor.utils.ts | 5 +- .../TableEditorLayout/TableEditorLayout.tsx | 1 + studio/stores/pgmeta/MetaStore.ts | 65 +++++++++++++++++-- 7 files changed, 117 insertions(+), 21 deletions(-) diff --git a/studio/components/interfaces/Database/Publications/PublicationsTableItem.tsx b/studio/components/interfaces/Database/Publications/PublicationsTableItem.tsx index 63074d6b764..2fa4a01b8eb 100644 --- a/studio/components/interfaces/Database/Publications/PublicationsTableItem.tsx +++ b/studio/components/interfaces/Database/Publications/PublicationsTableItem.tsx @@ -54,11 +54,11 @@ const PublicationsTableItem: FC = ({ table, selectedPublication }) => { {table.name} {table.schema} - + {table.comment} -
+
{enabledForAllTables ? ( // @ts-ignore = ({ table, selectedPublication }) => { size="tiny" align="right" disabled={loading} - className="m-0 p-0 ml-2 mt-1 -mb-1" + className="m-0 ml-2 mt-1 -mb-1 p-0" checked={checked} onChange={() => toggleReplicationForTable(table, publication)} /> diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx index eff9bad6c29..4b49a21fecd 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx @@ -148,13 +148,15 @@ const SidePanelEditor: FC = ({ tableId?: number importContent?: ImportContent isRLSEnabled: boolean + isRealtimeEnabled: boolean isDuplicateRows: boolean }, resolve: any ) => { let toastId let saveTableError = false - const { tableId, importContent, isRLSEnabled, isDuplicateRows } = configuration + const { tableId, importContent, isRLSEnabled, isRealtimeEnabled, isDuplicateRows } = + configuration try { if (isDuplicating) { @@ -163,6 +165,8 @@ const SidePanelEditor: FC = ({ category: 'loading', message: `Duplicating table: ${duplicateTable.name}...`, }) + + // Need to duplicate realtime perhaps? const table: any = await meta.duplicateTable(payload, { isRLSEnabled, isDuplicateRows, @@ -179,7 +183,14 @@ const SidePanelEditor: FC = ({ category: 'loading', message: `Creating new table: ${payload.name}...`, }) - const table = await meta.createTable(toastId, payload, isRLSEnabled, columns, importContent) + const table = await meta.createTable( + toastId, + payload, + columns, + isRLSEnabled, + isRealtimeEnabled, + importContent + ) ui.setNotification({ id: toastId, category: 'success', @@ -195,7 +206,8 @@ const SidePanelEditor: FC = ({ toastId, selectedTableToEdit, payload, - columns + columns, + isRealtimeEnabled ) if (hasError) { ui.setNotification({ @@ -258,8 +270,6 @@ const SidePanelEditor: FC = ({ )} = ({ table, - tables = [], - enumTypes = [] as PostgresType[], selectedSchema, isDuplicating, visible = false, @@ -52,9 +49,23 @@ const TableEditor: FC = ({ saveChanges = () => {}, updateEditorDirty = () => {}, }) => { - const { ui } = useStore() + const { ui, meta } = useStore() const isNewRecord = isUndefined(table) + const tables = meta.tables.list() + const enumTypes = meta.types.list( + (type: PostgresType) => !meta.excludedSchemas.includes(type.schema) + ) + + const publications = meta.publications.list() + const realtimePublication = publications.find( + (publication) => publication.name === 'supabase_realtime' + ) + const realtimeEnabledTables = realtimePublication?.tables ?? [] + const isRealtimeEnabled = isNewRecord + ? false + : realtimeEnabledTables.some((t: any) => t.id === table.id) + const [errors, setErrors] = useState({}) const [tableFields, setTableFields] = useState() const [isDuplicateRows, setIsDuplicateRows] = useState(false) @@ -70,7 +81,11 @@ const TableEditor: FC = ({ const tableFields = generateTableField() setTableFields(tableFields) } else { - const tableFields = generateTableFieldFromPostgresTable(table!, isDuplicating) + const tableFields = generateTableFieldFromPostgresTable( + table!, + isDuplicating, + isRealtimeEnabled + ) setTableFields(tableFields) } } @@ -122,6 +137,7 @@ const TableEditor: FC = ({ tableId: table?.id, importContent, isRLSEnabled: tableFields.isRLSEnabled, + isRealtimeEnabled: tableFields.isRealtimeEnabled, isDuplicateRows: isDuplicateRows, } @@ -187,8 +203,16 @@ const TableEditor: FC = ({
} description="Restrict access to your table by enabling RLS and writing Postgres policies" - checked={tableFields?.isRLSEnabled} - onChange={() => onUpdateField({ isRLSEnabled: !tableFields?.isRLSEnabled })} + checked={tableFields.isRLSEnabled} + onChange={() => onUpdateField({ isRLSEnabled: !tableFields.isRLSEnabled })} + size="medium" + /> + onUpdateField({ isRealtimeEnabled: !tableFields.isRealtimeEnabled })} size="medium" />
diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.types.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.types.ts index 4e7d4ecdc64..da2fc543765 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.types.ts +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.types.ts @@ -7,6 +7,7 @@ export interface TableField { comment?: string columns: ColumnField[] isRLSEnabled: boolean + isRealtimeEnabled: boolean } export interface ImportContent { diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.utils.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.utils.ts index 2e02f1ff191..b17c714db37 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.utils.ts +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.utils.ts @@ -30,12 +30,14 @@ export const generateTableField = (): TableField => { comment: '', columns: DEFAULT_COLUMNS, isRLSEnabled: false, + isRealtimeEnabled: false, } } export const generateTableFieldFromPostgresTable = ( table: PostgresTable, - isDuplicating = false + isDuplicating = false, + isRealtimeEnabled = false ): TableField => { return { id: table.id, @@ -46,6 +48,7 @@ export const generateTableFieldFromPostgresTable = ( return generateColumnFieldFromPostgresColumn(column, table) }), isRLSEnabled: table.rls_enabled, + isRealtimeEnabled, } } diff --git a/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx b/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx index 7a73bb73d18..1ab3f658cdd 100644 --- a/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx +++ b/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx @@ -37,6 +37,7 @@ const TableEditorLayout: FC = ({ meta.schemas.load() meta.tables.load() meta.types.load() + meta.publications.load() } }, [ui.selectedProject]) diff --git a/studio/stores/pgmeta/MetaStore.ts b/studio/stores/pgmeta/MetaStore.ts index 874bfa65ada..964bf5192c8 100644 --- a/studio/stores/pgmeta/MetaStore.ts +++ b/studio/stores/pgmeta/MetaStore.ts @@ -98,11 +98,18 @@ export interface IMetaStore { createTable: ( toastId: string, payload: any, - isRLSEnabled: boolean, columns: ColumnField[], + isRLSEnabled: boolean, + isRealtimeEnabled: boolean, importContent?: ImportContent ) => any - updateTable: (toastId: string, table: PostgresTable, payload: any, columns: ColumnField[]) => any + updateTable: ( + toastId: string, + table: PostgresTable, + payload: any, + columns: ColumnField[], + isRealtimeEnabled: boolean + ) => any } export default class MetaStore implements IMetaStore { rootStore: IRootStore @@ -332,6 +339,7 @@ export default class MetaStore implements IMetaStore { // Insert into does not copy over auto increment sequences, so we manually do it next if any const columns = metadata.duplicateTable.columns + // @ts-ignore const identityColumns = columns.filter((column) => column.identity_generation !== null) identityColumns.map(async (column) => { const identity = await this.rootStore.meta.query( @@ -358,8 +366,9 @@ export default class MetaStore implements IMetaStore { async createTable( toastId: string, payload: any, - isRLSEnabled: boolean, columns: ColumnField[] = [], + isRLSEnabled: boolean, + isRealtimeEnabled: boolean, importContent?: ImportContent ) { // Create the table first @@ -379,6 +388,22 @@ export default class MetaStore implements IMetaStore { if (updatedTable.error) throw updatedTable.error } + // Toggle Realtime if configured to be + if (isRealtimeEnabled) { + const publications = this.publications.list() + const realtimePublication = publications.find( + (publication) => publication.name === 'supabase_realtime' + ) + + const id = realtimePublication?.id + const realtimeTables = [`${table.schema}.${table.name}`].concat( + realtimePublication.tables.map((t: any) => `${t.schema}.${t.name}`) + ) + let payload = { id, tables: realtimeTables } + const { error: publicationsUpdateError } = await this.publications.update(id, payload) + if (publicationsUpdateError) throw publicationsUpdateError + } + // Then insert the columns - we don't do Promise.all as we want to keep the integrity // of the column order during creation. Note that we add primary key constraints separately // via the query endpoint to support composite primary keys as pg-meta does not support that OOB @@ -483,11 +508,18 @@ export default class MetaStore implements IMetaStore { } } - async updateTable(toastId: string, table: PostgresTable, payload: any, columns: ColumnField[]) { + async updateTable( + toastId: string, + table: PostgresTable, + payload: any, + columns: ColumnField[], + isRealtimeEnabled: boolean + ) { // Prepare a check to see if primary keys to the tables were updated or not const primaryKeyColumns = columns .filter((column) => column.isPrimaryKey) .map((column) => `"${column.name}"`) + // @ts-ignore const existingPrimaryKeyColumns = table.primary_keys.map( (pk: PostgresPrimaryKey) => `"${pk.name}"` ) @@ -512,6 +544,7 @@ export default class MetaStore implements IMetaStore { const columnIds = columns.map((column) => column.id) // Delete any removed columns + // @ts-ignore const columnsToRemove = originalColumns.filter((column) => !columnIds.includes(column.id)) for (const column of columnsToRemove) { this.rootStore.ui.setNotification({ @@ -583,6 +616,30 @@ export default class MetaStore implements IMetaStore { if (primaryKeys.error) throw primaryKeys.error } + // Update realtime configuration + const publications = this.publications.list() + const realtimePublication = publications.find( + (publication) => publication.name === 'supabase_realtime' + ) + + const id = realtimePublication?.id + const realtimeEnabled = realtimePublication.tables.some((x: any) => x.id == table.id) + if (realtimeEnabled && !isRealtimeEnabled) { + // Toggle realtime off + const realtimeTables = realtimePublication.tables + .filter((x: any) => x.id != table.id) + .map((x: any) => `${x.schema}.${x.name}`) + let payload = { id, tables: realtimeTables } + await this.publications.update(id, payload) + } else if (!realtimeEnabled && isRealtimeEnabled) { + // Toggle realtime on + const realtimeTables = [`${table.schema}.${table.name}`].concat( + realtimePublication.tables.map((t: any) => `${t.schema}.${t.name}`) + ) + let payload = { id, tables: realtimeTables } + await this.publications.update(id, payload) + } + return { table: await this.tables.loadById(table.id), hasError } } From 8748bf6d3cafb9993e327dc2f12969031a99efe5 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Mon, 20 Jun 2022 14:49:56 +0800 Subject: [PATCH 39/57] Ensure realtime toggling works for duplicating table as well --- .../SidePanelEditor/SidePanelEditor.tsx | 3 +- studio/stores/pgmeta/MetaStore.ts | 42 +++++++++++++++---- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx index 4b49a21fecd..e1403753d2d 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx @@ -165,10 +165,9 @@ const SidePanelEditor: FC = ({ category: 'loading', message: `Duplicating table: ${duplicateTable.name}...`, }) - - // Need to duplicate realtime perhaps? const table: any = await meta.duplicateTable(payload, { isRLSEnabled, + isRealtimeEnabled, isDuplicateRows, duplicateTable, }) diff --git a/studio/stores/pgmeta/MetaStore.ts b/studio/stores/pgmeta/MetaStore.ts index 964bf5192c8..986680b48dc 100644 --- a/studio/stores/pgmeta/MetaStore.ts +++ b/studio/stores/pgmeta/MetaStore.ts @@ -93,7 +93,12 @@ export interface IMetaStore { ) => any duplicateTable: ( payload: any, - metadata: { duplicateTable: PostgresTable; isRLSEnabled: boolean; isDuplicateRows: boolean } + metadata: { + duplicateTable: PostgresTable + isRLSEnabled: boolean + isRealtimeEnabled: boolean + isDuplicateRows: boolean + } ) => void createTable: ( toastId: string, @@ -304,9 +309,15 @@ export default class MetaStore implements IMetaStore { async duplicateTable( payload: any, - metadata: { duplicateTable: PostgresTable; isRLSEnabled: boolean; isDuplicateRows: boolean } + metadata: { + duplicateTable: PostgresTable + isRLSEnabled: boolean + isRealtimeEnabled: boolean + isDuplicateRows: boolean + } ) { - const sourceTableName = metadata.duplicateTable.name + const { duplicateTable, isRLSEnabled, isRealtimeEnabled, isDuplicateRows } = metadata + const sourceTableName = duplicateTable.name const duplicatedTableName = payload.name // The following query will copy the structure of the table along with indexes, constraints and @@ -317,7 +328,7 @@ export default class MetaStore implements IMetaStore { if (table.error) throw table.error // Duplicate foreign key constraints over - const relationships = metadata.duplicateTable.relationships + const relationships = duplicateTable.relationships if (relationships.length > 0) { // @ts-ignore, but might need to investigate, sounds bad: // Type instantiation is excessively deep and possibly infinite @@ -331,14 +342,14 @@ export default class MetaStore implements IMetaStore { } // Duplicate rows if needed - if (metadata.isDuplicateRows) { + if (isDuplicateRows) { const rows = await this.rootStore.meta.query( `INSERT INTO "${duplicatedTableName}" SELECT * FROM ${sourceTableName};` ) if (rows.error) throw rows.error // Insert into does not copy over auto increment sequences, so we manually do it next if any - const columns = metadata.duplicateTable.columns + const columns = duplicateTable.columns // @ts-ignore const identityColumns = columns.filter((column) => column.identity_generation !== null) identityColumns.map(async (column) => { @@ -353,13 +364,28 @@ export default class MetaStore implements IMetaStore { const tables = this.tables.list() const duplicatedTable = find(tables, { name: duplicatedTableName }) - if (metadata.isRLSEnabled) { + if (isRLSEnabled) { const updateTable: any = await this.tables.update(duplicatedTable!.id, { - rls_enabled: metadata.isRLSEnabled, + rls_enabled: isRLSEnabled, }) if (updateTable.error) throw updateTable.error } + if (isRealtimeEnabled) { + const publications = this.publications.list() + const realtimePublication = publications.find( + (publication) => publication.name === 'supabase_realtime' + ) + + const id = realtimePublication?.id + const realtimeTables = [`${duplicateTable.schema}.${duplicatedTableName}`].concat( + realtimePublication.tables.map((t: any) => `${t.schema}.${t.name}`) + ) + let payload = { id, tables: realtimeTables } + const { error: publicationsUpdateError } = await this.publications.update(id, payload) + if (publicationsUpdateError) throw publicationsUpdateError + } + return duplicatedTable } From 8fccf1c18cd7d9b58fa445fee25b59bbec4b92ee Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Mon, 20 Jun 2022 16:40:20 +0800 Subject: [PATCH 40/57] Fix type errors and update type file for uuid --- .../TableEditor/TableEditor.tsx | 2 +- studio/package-lock.json | 63 ++++++++++++++++--- 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx index 4393cfbf842..d03c3478f9b 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx @@ -64,7 +64,7 @@ const TableEditor: FC = ({ const realtimeEnabledTables = realtimePublication?.tables ?? [] const isRealtimeEnabled = isNewRecord ? false - : realtimeEnabledTables.some((t: any) => t.id === table.id) + : realtimeEnabledTables.some((t: any) => t.id === table?.id) const [errors, setErrors] = useState({}) const [tableFields, setTableFields] = useState() diff --git a/studio/package-lock.json b/studio/package-lock.json index 346948ff30f..5a72584b7e0 100644 --- a/studio/package-lock.json +++ b/studio/package-lock.json @@ -70,6 +70,7 @@ "swr": "^1.2.1", "uniforms-bootstrap4": "^3.6.2", "uniforms-bridge-json-schema": "^3.6.2", + "uuid": "^8.3.2", "zxcvbn": "^4.4.2" }, "devDependencies": { @@ -100,6 +101,7 @@ "@types/react-window-infinite-loader": "^1.0.5", "@types/semver": "^7.3.9", "@types/sqlstring": "^2.3.0", + "@types/uuid": "^8.3.4", "@types/zxcvbn": "^4.4.1", "autoprefixer": "^10.4.2", "babel-loader": "^8.2.3", @@ -9048,6 +9050,12 @@ "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", "dev": true }, + "node_modules/@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", + "dev": true + }, "node_modules/@types/webpack": { "version": "4.41.32", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.32.tgz", @@ -9472,6 +9480,15 @@ "node": ">=4" } }, + "node_modules/analytics-node/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/anser": { "version": "1.4.9", "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.9.tgz", @@ -27444,12 +27461,11 @@ } }, "node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "bin": { - "uuid": "bin/uuid" + "uuid": "dist/bin/uuid" } }, "node_modules/uuid-browser": { @@ -28143,6 +28159,16 @@ "node": ">= 6" } }, + "node_modules/webpack-log/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/webpack-sources": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", @@ -35769,6 +35795,12 @@ "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", "dev": true }, + "@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", + "dev": true + }, "@types/webpack": { "version": "4.41.32", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.32.tgz", @@ -36151,6 +36183,13 @@ "ms": "^2.0.0", "remove-trailing-slash": "^0.1.0", "uuid": "^3.2.1" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + } } }, "anser": { @@ -49981,9 +50020,9 @@ "dev": true }, "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, "uuid-browser": { "version": "3.1.0", @@ -50845,6 +50884,14 @@ "requires": { "ansi-colors": "^3.0.0", "uuid": "^3.3.2" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } } }, "webpack-sources": { From db00b3f8dbd0e2a2a2b0fb3d024da5381cb85e53 Mon Sep 17 00:00:00 2001 From: Belkacem Bouzerzour Date: Tue, 21 Jun 2022 09:18:37 +0100 Subject: [PATCH 41/57] Update local-development.mdx Fix a typo --- web/docs/guides/local-development.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/docs/guides/local-development.mdx b/web/docs/guides/local-development.mdx index c1a27241444..114c5f8cfcf 100644 --- a/web/docs/guides/local-development.mdx +++ b/web/docs/guides/local-development.mdx @@ -248,7 +248,7 @@ The local development environment is not as feature-complete as the Platform. He - The Storage interface is coming soon. - The Functions interface is coming soon. -- Logs are not supported though the interface (however you can accesss them via the docker containers). +- Logs are not supported through the interface (however you can accesss them via the docker containers). - You cannot update your project settings via the Dashboard - this can be done using the CLI instead. ## Next steps From 84262d36e49214b48bd62a5a0e190b4e74691f1a Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 21 Jun 2022 15:00:57 +0800 Subject: [PATCH 42/57] Midway: Fix for pagination not clearing selected rows --- studio/components/grid/SupabaseGrid.tsx | 233 +++++++++--------- .../grid/components/common/ConfirmAlert.tsx | 66 +++-- .../footer/pagination/Pagination.tsx | 125 ++++++---- .../components/grid/components/grid/Grid.tsx | 9 +- studio/components/grid/store/reducers/row.ts | 83 +++---- studio/components/grid/utils/row.ts | 29 +-- 6 files changed, 280 insertions(+), 265 deletions(-) diff --git a/studio/components/grid/SupabaseGrid.tsx b/studio/components/grid/SupabaseGrid.tsx index 4f192d036d5..c740fd78b8a 100644 --- a/studio/components/grid/SupabaseGrid.tsx +++ b/studio/components/grid/SupabaseGrid.tsx @@ -1,40 +1,33 @@ -import React from 'react'; -import { createPortal } from 'react-dom'; -import { useMonaco } from '@monaco-editor/react'; -import { DndProvider } from 'react-dnd'; -import { HTML5Backend } from 'react-dnd-html5-backend'; -import { Dictionary, SupabaseGridProps, SupabaseGridRef } from './types'; -import { DataGridHandle } from '@supabase/react-data-grid'; -import { RowContextMenu } from './components/menu'; -import { StoreProvider, useDispatch, useTrackedState } from './store'; -import { fetchCount, fetchPage, refreshPageDebounced } from './utils'; -import { REFRESH_PAGE_IMMEDIATELY, TOTAL_ROWS_RESET } from './constants'; -import { Grid } from './components/grid'; -import { Shortcuts } from './components/common'; -import Header from './components/header'; -import Footer from './components/footer'; -import { - cleanupProps, - initTable, - saveStorageDebounced, -} from './SupabaseGrid.utils'; +import { useEffect, useState, useRef, forwardRef, useImperativeHandle } from 'react' +import { createPortal } from 'react-dom' +import { useMonaco } from '@monaco-editor/react' +import { DndProvider } from 'react-dnd' +import { HTML5Backend } from 'react-dnd-html5-backend' +import { Dictionary, SupabaseGridProps, SupabaseGridRef } from './types' +import { DataGridHandle } from '@supabase/react-data-grid' +import { RowContextMenu } from './components/menu' +import { StoreProvider, useDispatch, useTrackedState } from './store' +import { fetchCount, fetchPage, refreshPageDebounced } from './utils' +import { REFRESH_PAGE_IMMEDIATELY, TOTAL_ROWS_RESET } from './constants' +import { Grid } from './components/grid' +import { Shortcuts } from './components/common' +import Header from './components/header' +import Footer from './components/footer' +import { cleanupProps, initTable, saveStorageDebounced } from './SupabaseGrid.utils' /** * Supabase Grid. * * React component to render database table. */ -export const SupabaseGrid = React.forwardRef< - SupabaseGridRef, - SupabaseGridProps ->((props, ref) => { - const monaco = useMonaco(); - const _props = cleanupProps(props); - const { theme } = _props; +export const SupabaseGrid = forwardRef((props, ref) => { + const monaco = useMonaco() + const _props = cleanupProps(props) + const { theme } = _props - React.useEffect(() => { + useEffect(() => { if (monaco) { - const darkTheme = theme && theme === 'dark' ? true : false; + const darkTheme = theme && theme === 'dark' ? true : false monaco.editor.defineTheme('supabase', { base: 'vs-dark', // can also be vs-dark or hc-black @@ -47,9 +40,9 @@ export const SupabaseGrid = React.forwardRef< colors: { 'editor.background': darkTheme ? '#1f1f1f' : '#30313f', }, - }); + }) } - }, [monaco]); + }, [monaco]) return ( @@ -57,103 +50,101 @@ export const SupabaseGrid = React.forwardRef< - ); -}); + ) +}) -const SupabaseGridLayout = React.forwardRef( - (props, ref) => { - const { editable, storageRef, gridProps, headerActions } = props; - const dispatch = useDispatch(); - const state = useTrackedState(); - const gridRef = React.useRef(null); - const [mounted, setMount] = React.useState(false); +const SupabaseGridLayout = forwardRef((props, ref) => { + const { editable, storageRef, gridProps, headerActions } = props + const dispatch = useDispatch() + const state = useTrackedState() + const gridRef = useRef(null) + const [mounted, setMount] = useState(false) - React.useImperativeHandle(ref, () => ({ - rowAdded(row: Dictionary) { - dispatch({ - type: 'ADD_NEW_ROW', - payload: row, - }); - }, - rowEdited(row: Dictionary, idx: number) { - dispatch({ - type: 'EDIT_ROW', - payload: { row, idx }, - }); - }, - })); + useImperativeHandle(ref, () => ({ + rowAdded(row: Dictionary) { + dispatch({ + type: 'ADD_NEW_ROW', + payload: row, + }) + }, + rowEdited(row: Dictionary, idx: number) { + dispatch({ + type: 'EDIT_ROW', + payload: { row, idx }, + }) + }, + })) - React.useEffect(() => { - if (!mounted) setMount(true); - }, []); + useEffect(() => { + if (!mounted) setMount(true) + }, []) - React.useEffect(() => { - if (state.isInitialComplete && storageRef && state.table) { - saveStorageDebounced(state, storageRef); - } - }, [ - state.table, - state.isInitialComplete, - state.gridColumns, - state.sorts, - state.filters, - storageRef, - ]); + useEffect(() => { + if (state.isInitialComplete && storageRef && state.table) { + saveStorageDebounced(state, storageRef) + } + }, [ + state.table, + state.isInitialComplete, + state.gridColumns, + state.sorts, + state.filters, + storageRef, + ]) - React.useEffect(() => { - if (state.refreshPageFlag == REFRESH_PAGE_IMMEDIATELY) { - fetchPage(state, dispatch); - } else if (state.refreshPageFlag != 0) { - refreshPageDebounced(state, dispatch); - } - }, [state.refreshPageFlag]); + useEffect(() => { + if (state.refreshPageFlag == REFRESH_PAGE_IMMEDIATELY) { + fetchPage(state, dispatch) + } else if (state.refreshPageFlag != 0) { + refreshPageDebounced(state, dispatch) + } + }, [state.refreshPageFlag]) - React.useEffect(() => { - if (state.totalRows === TOTAL_ROWS_RESET) { - fetchCount(state, dispatch); - } - }, [state.totalRows]); + useEffect(() => { + if (state.totalRows === TOTAL_ROWS_RESET) { + fetchCount(state, dispatch) + } + }, [state.totalRows]) - React.useEffect(() => { - if (!state.metaService) { - dispatch({ - type: 'INIT_CLIENT', - payload: { onSqlQuery: props.onSqlQuery }, - }); - dispatch({ - type: 'INIT_CALLBACK', - payload: { ...props }, - }); - } - }, [state.metaService]); + useEffect(() => { + if (!state.metaService) { + dispatch({ + type: 'INIT_CLIENT', + payload: { onSqlQuery: props.onSqlQuery }, + }) + dispatch({ + type: 'INIT_CALLBACK', + payload: { ...props }, + }) + } + }, [state.metaService]) - React.useEffect(() => { - if (!state.metaService) return; + useEffect(() => { + if (!state.metaService) return - if ( - !state.table || - (typeof props.table == 'string' && - state.table!.name != props.table && - state.table!.schema != props.schema) || - (typeof props.table != 'string' && - JSON.stringify(props.table) !== JSON.stringify(state.table)) - ) { - initTable(props, state, dispatch); - } - }, [state.metaService, state.table, props.table, props.schema]); + if ( + !state.table || + (typeof props.table == 'string' && + state.table!.name != props.table && + state.table!.schema != props.schema) || + (typeof props.table != 'string' && + JSON.stringify(props.table) !== JSON.stringify(state.table)) + ) { + initTable(props, state, dispatch) + } + }, [state.metaService, state.table, props.table, props.schema]) - return ( -
-
- -
- - {mounted && createPortal(, document.body)} -
- ); - } -); + return ( +
+
+ +
+ + {mounted && createPortal(, document.body)} +
+ ) +}) diff --git a/studio/components/grid/components/common/ConfirmAlert.tsx b/studio/components/grid/components/common/ConfirmAlert.tsx index d1e0dd7f03a..294e3916cc0 100644 --- a/studio/components/grid/components/common/ConfirmAlert.tsx +++ b/studio/components/grid/components/common/ConfirmAlert.tsx @@ -1,14 +1,14 @@ -import * as React from 'react'; -import { Button, Modal } from '@supabase/ui'; -import { render, unmountComponentAtNode } from 'react-dom'; +import * as React from 'react' +import { Button, Modal } from '@supabase/ui' +import { render, unmountComponentAtNode } from 'react-dom' type ConfirmModalProps = { - title: string; - message: string; - onConfirm?: () => void; - onAsyncConfirm?: () => Promise; - variant?: 'danger' | 'warning' | 'success' | undefined; -}; + title: string + message: string + onConfirm?: () => void + onAsyncConfirm?: () => Promise + variant?: 'danger' | 'warning' | 'success' | undefined +} const ConfirmModal: React.FC = ({ title, message, @@ -16,26 +16,26 @@ const ConfirmModal: React.FC = ({ onAsyncConfirm, variant = 'danger', }) => { - const [loading, setLoading] = React.useState(false); + const [loading, setLoading] = React.useState(false) function onCancelClick() { - if (!loading) onClose(); + if (!loading) onClose() } async function onConfirmClick() { if (onAsyncConfirm) { - setLoading(true); - await onAsyncConfirm(); + setLoading(true) + await onAsyncConfirm() - onClose(); + onClose() } else if (onConfirm) { - onConfirm(); - onClose(); + onConfirm() + onClose() } } function onClose() { - removeElement(); + removeElement() } return ( @@ -55,11 +55,7 @@ const ConfirmModal: React.FC = ({ -
@@ -67,32 +63,32 @@ const ConfirmModal: React.FC = ({ } > -

{message}

+

{message}

- ); -}; + ) +} function removeElement() { - const target = document.getElementById('supabase-ui-confirm-alert'); + const target = document.getElementById('supabase-ui-confirm-alert') if (target) { - unmountComponentAtNode(target); - target?.parentNode?.removeChild(target); + unmountComponentAtNode(target) + target?.parentNode?.removeChild(target) } } function createElement(props: ConfirmModalProps) { - let divTarget = document.getElementById('supabase-ui-confirm-alert'); + let divTarget = document.getElementById('supabase-ui-confirm-alert') if (divTarget) { - render(, divTarget); + render(, divTarget) } else { - divTarget = document.createElement('div'); - divTarget.id = 'supabase-ui-confirm-alert'; - document.body.appendChild(divTarget); - render(, divTarget); + divTarget = document.createElement('div') + divTarget.id = 'supabase-ui-confirm-alert' + document.body.appendChild(divTarget) + render(, divTarget) } } export function showConfirmAlert(props: ConfirmModalProps) { - createElement(props); + createElement(props) } diff --git a/studio/components/grid/components/footer/pagination/Pagination.tsx b/studio/components/grid/components/footer/pagination/Pagination.tsx index 4e9a317db08..3cfa45b07fc 100644 --- a/studio/components/grid/components/footer/pagination/Pagination.tsx +++ b/studio/components/grid/components/footer/pagination/Pagination.tsx @@ -1,73 +1,106 @@ -import * as React from 'react'; -import AwesomeDebouncePromise from 'awesome-debounce-promise'; -import { - Button, - InputNumber, - Typography, - IconArrowRight, - IconArrowLeft, -} from '@supabase/ui'; -import { DropdownControl } from '../../common'; -import { useDispatch, useTrackedState } from '../../../store'; +import { FC, useState, useEffect, useRef } from 'react' +import AwesomeDebouncePromise from 'awesome-debounce-promise' +import { Button, InputNumber, IconArrowRight, IconArrowLeft } from '@supabase/ui' +import { DropdownControl, showConfirmAlert } from '../../common' +import { useDispatch, useTrackedState } from '../../../store' const updatePage = (payload: number, dispatch: (value: unknown) => void) => { dispatch({ type: 'SET_PAGE', payload: payload, - }); -}; -const updatePageDebounced = AwesomeDebouncePromise(updatePage, 550); + }) +} +const updatePageDebounced = AwesomeDebouncePromise(updatePage, 550) const rowsPerPageOptions = [ { value: 100, label: '100 rows' }, { value: 500, label: '500 rows' }, { value: 1000, label: '1000 rows' }, -]; +] -type PaginationProps = {}; +type PaginationProps = {} -const Pagination: React.FC = () => { - const state = useTrackedState(); - const dispatch = useDispatch(); - const [page, setPage] = React.useState(state.page); - const maxPages = Math.ceil(state.totalRows / state.rowsPerPage); - const totalPages = state.totalRows > 0 ? maxPages : 1; +const Pagination: FC = () => { + const state = useTrackedState() + const dispatch = useDispatch() + const [page, setPage] = useState(state.page) + const maxPages = Math.ceil(state.totalRows / state.rowsPerPage) + const totalPages = state.totalRows > 0 ? maxPages : 1 - React.useEffect(() => { - if (state.page != page) setPage(state.page); - }, [state.page, page]); + useEffect(() => { + if (state.page != page) setPage(state.page) + }, [state.page, page]) - function onPreviousPage() { + // [Joshen] Oddly the methods here seems to be getting stale state + console.log('Pagination', state.selectedRows) + + const onPreviousPage = () => { if (state.page > 1) { - const previousPage = state.page - 1; - setPage(previousPage); - dispatch({ type: 'SET_PAGE', payload: previousPage }); + if (state.selectedRows.size >= 1) { + showConfirmAlert({ + title: 'Confirm moving to previous page', + message: 'The currently selected lines will be deselected, do you want to proceed?', + onConfirm: () => { + goToPreviousPage() + dispatch({ + type: 'SELECTED_ROWS_CHANGE', + payload: { selectedRows: new Set() }, + }) + }, + }) + } else { + goToPreviousPage() + } } } - function onNextPage() { + const onNextPage = () => { if (state.page < maxPages) { - const nextPage = state.page + 1; - setPage(nextPage); - dispatch({ type: 'SET_PAGE', payload: nextPage }); + if (state.selectedRows.size >= 1) { + showConfirmAlert({ + title: 'Confirm moving to next page', + message: 'The currently selected lines will be deselected, do you want to proceed?', + onConfirm: () => { + goToNextPage() + dispatch({ + type: 'SELECTED_ROWS_CHANGE', + payload: { selectedRows: new Set() }, + }) + }, + }) + } else { + goToNextPage() + } } } + const goToPreviousPage = () => { + const previousPage = state.page - 1 + setPage(previousPage) + dispatch({ type: 'SET_PAGE', payload: previousPage }) + } + + const goToNextPage = () => { + const nextPage = state.page + 1 + setPage(nextPage) + dispatch({ type: 'SET_PAGE', payload: nextPage }) + } + function onPageChange(event: React.ChangeEvent) { - const value = event.target.value; - const pageNum = Number(value); - setPage(pageNum); - updatePageDebounced(pageNum, dispatch); + const value = event.target.value + const pageNum = Number(value) + setPage(pageNum) + updatePageDebounced(pageNum, dispatch) } function onRowsPerPageChange(value: string | number) { - dispatch({ type: 'SET_ROWS_PER_PAGE', payload: value }); + dispatch({ type: 'SET_ROWS_PER_PAGE', payload: value }) } return (
{state.totalRows < 0 ? ( - ... loading total records +

Loading records...

) : ( <> - - {`${state.totalRows.toLocaleString()} records`} - +

{`${state.totalRows.toLocaleString()} records`}

)}
- ); -}; -export default Pagination; + ) +} +export default Pagination diff --git a/studio/components/grid/components/grid/Grid.tsx b/studio/components/grid/components/grid/Grid.tsx index e49d199a13a..b9e774ac1ca 100644 --- a/studio/components/grid/components/grid/Grid.tsx +++ b/studio/components/grid/components/grid/Grid.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { forwardRef } from 'react' import { memo } from 'react-tracked' import DataGrid, { DataGridHandle, RowsChangeData } from '@supabase/react-data-grid' import { IconLoader } from '@supabase/ui' @@ -12,7 +12,7 @@ function rowKeyGetter(row: SupaRow) { } export const Grid = memo( - React.forwardRef( + forwardRef( ( { width, height, containerClass, gridClass, rowClass }, ref: React.Ref | undefined @@ -23,6 +23,9 @@ export const Grid = memo( const columnHeaders = state.gridColumns.map((x) => `${x.key}_${x.frozen}`) const { gridColumns, rows, onError: onErrorFunc } = state + // PROBLEM MIGHT BE HERE - MEMO IS NOT LETTING THE GRID RERENDER + console.log('Rows', rows) + function onColumnResize(index: number, width: number) { updateColumnResizeDebounced(index, width, dispatch) } @@ -52,6 +55,7 @@ export const Grid = memo( } function onSelectedRowsChange(selectedRows: ReadonlySet) { + console.log('onSelectedRowsChange', selectedRows) dispatch({ type: 'SELECTED_ROWS_CHANGE', payload: { selectedRows }, @@ -80,6 +84,7 @@ export const Grid = memo(
) } + return (
; - selectedCellPosition: { idx: number; rowIdx: number } | null; - page: number; - rowsPerPage: number; - totalRows: number; + rows: SupaRow[] + selectedRows: ReadonlySet + selectedCellPosition: { idx: number; rowIdx: number } | null + page: number + rowsPerPage: number + totalRows: number } export const rowInitialState: RowInitialState = { @@ -23,32 +19,32 @@ export const rowInitialState: RowInitialState = { page: 1, rowsPerPage: 100, totalRows: TOTAL_ROWS_INITIAL, -}; +} type ROW_ACTIONTYPE = | INIT_ACTIONTYPE | { - type: 'SELECTED_CELL_CHANGE'; - payload: { position: { idx: number; rowIdx: number } }; + type: 'SELECTED_CELL_CHANGE' + payload: { position: { idx: number; rowIdx: number } } } | { - type: 'SELECTED_ROWS_CHANGE'; - payload: { selectedRows: ReadonlySet }; + type: 'SELECTED_ROWS_CHANGE' + payload: { selectedRows: ReadonlySet } } | { type: 'SET_PAGE'; payload: number } | { type: 'SET_ROWS_PER_PAGE'; payload: number } | { - type: 'SET_ROWS'; - payload: { rows: SupaRow[] }; + type: 'SET_ROWS' + payload: { rows: SupaRow[] } } | { - type: 'SET_ROWS_COUNT'; - payload: number; + type: 'SET_ROWS_COUNT' + payload: number } | { type: 'ADD_ROWS'; payload: SupaRow[] } | { type: 'ADD_NEW_ROW'; payload: Dictionary } | { type: 'EDIT_ROW'; payload: { row: Dictionary; idx: number } } - | { type: 'REMOVE_ROWS'; payload: { rowIdxs: number[] } }; + | { type: 'REMOVE_ROWS'; payload: { rowIdxs: number[] } } const RowReducer = (state: RowInitialState, action: ROW_ACTIONTYPE) => { switch (action.type) { @@ -59,26 +55,26 @@ const RowReducer = (state: RowInitialState, action: ROW_ACTIONTYPE) => { selectedCellPosition: null, selectedRows: new Set(), totalRows: TOTAL_ROWS_RESET, - }; + } } case 'SELECTED_CELL_CHANGE': { return { ...state, selectedCellPosition: action.payload.position, - }; + } } case 'SELECTED_ROWS_CHANGE': { return { ...state, selectedRows: action.payload.selectedRows, - }; + } } case 'SET_PAGE': { return { ...state, page: action.payload, refreshPageFlag: REFRESH_PAGE_IMMEDIATELY, - }; + } } case 'SET_ROWS_PER_PAGE': { return { @@ -86,58 +82,59 @@ const RowReducer = (state: RowInitialState, action: ROW_ACTIONTYPE) => { page: 1, rowsPerPage: action.payload, refreshPageFlag: REFRESH_PAGE_IMMEDIATELY, - }; + } } case 'SET_ROWS': { + console.log('SET_ROWS', action.payload.rows[0]) return { ...state, rows: action.payload.rows, refreshPageFlag: 0, - }; + } } case 'SET_ROWS_COUNT': { return { ...state, totalRows: action.payload, - }; + } } case 'ADD_ROWS': { - const totalRows = state.totalRows + action.payload.length; + const totalRows = state.totalRows + action.payload.length return { ...state, rows: update(state.rows, { $push: action.payload }), totalRows: totalRows, - }; + } } case 'ADD_NEW_ROW': { - const supaRow = { ...action.payload, idx: state.rows.length }; - const totalRows = state.totalRows + 1; + const supaRow = { ...action.payload, idx: state.rows.length } + const totalRows = state.totalRows + 1 return { ...state, rows: update(state.rows, { $push: [supaRow] }), totalRows: totalRows, - }; + } } case 'EDIT_ROW': { - const supaRow = { ...action.payload.row, idx: action.payload.idx }; + const supaRow = { ...action.payload.row, idx: action.payload.idx } return { ...state, rows: update(state.rows, { [action.payload.idx]: { $set: supaRow }, }), - }; + } } case 'REMOVE_ROWS': { - const totalRows = state.totalRows - action.payload.rowIdxs.length; + const totalRows = state.totalRows - action.payload.rowIdxs.length return { ...state, rows: state.rows.filter((x) => !action.payload.rowIdxs.includes(x.idx)), totalRows: totalRows, - }; + } } default: - return state; + return state } -}; +} -export default RowReducer; +export default RowReducer diff --git a/studio/components/grid/utils/row.ts b/studio/components/grid/utils/row.ts index 718fa4b1d77..23e2f8943b7 100644 --- a/studio/components/grid/utils/row.ts +++ b/studio/components/grid/utils/row.ts @@ -1,40 +1,35 @@ -import AwesomeDebouncePromise from 'awesome-debounce-promise'; -import { InitialStateType } from '../store/reducers'; +import AwesomeDebouncePromise from 'awesome-debounce-promise' +import { InitialStateType } from '../store/reducers' -export async function fetchCount( - state: InitialStateType, - dispatch: (value: unknown) => void -) { - if (!state.rowService) return; - const { data, error } = await state.rowService.count(state.filters); +export async function fetchCount(state: InitialStateType, dispatch: (value: unknown) => void) { + if (!state.rowService) return + const { data, error } = await state.rowService.count(state.filters) if (error) { // TODO: handle fetch rows count error } else { dispatch({ type: 'SET_ROWS_COUNT', payload: data ?? 0, - }); + }) } } -export async function fetchPage( - state: InitialStateType, - dispatch: (value: unknown) => void -) { - if (!state.rowService) return; +export async function fetchPage(state: InitialStateType, dispatch: (value: unknown) => void) { + if (!state.rowService) return const { data, error } = await state.rowService.fetchPage( state.page, state.rowsPerPage, state.filters, state.sorts - ); + ) + console.log('Fetch page', { page: state.page, rowsPerPage: state.rowsPerPage }, data, error) if (error) { // TODO: handle fetch rows data error } else { dispatch({ type: 'SET_ROWS', payload: { rows: data?.rows ?? [] }, - }); + }) } } -export const refreshPageDebounced = AwesomeDebouncePromise(fetchPage, 500); +export const refreshPageDebounced = AwesomeDebouncePromise(fetchPage, 500) From 7ca9b91693dc51acda42072d9aee3b7b7c4730f7 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 21 Jun 2022 16:24:44 +0800 Subject: [PATCH 43/57] Pass rows as props into Grid component instead to ensure that component re-renders --- studio/components/grid/SupabaseGrid.tsx | 2 +- .../footer/pagination/Pagination.tsx | 6 ++--- .../components/grid/components/grid/Grid.tsx | 24 +++++++------------ studio/components/grid/store/reducers/row.ts | 1 - studio/components/grid/utils/row.ts | 1 - 5 files changed, 13 insertions(+), 21 deletions(-) diff --git a/studio/components/grid/SupabaseGrid.tsx b/studio/components/grid/SupabaseGrid.tsx index c740fd78b8a..6a09b66a572 100644 --- a/studio/components/grid/SupabaseGrid.tsx +++ b/studio/components/grid/SupabaseGrid.tsx @@ -141,7 +141,7 @@ const SupabaseGridLayout = forwardRef((props onAddColumn={editable ? props.onAddColumn : undefined} headerActions={headerActions} /> - +
{mounted && createPortal(, document.body)} diff --git a/studio/components/grid/components/footer/pagination/Pagination.tsx b/studio/components/grid/components/footer/pagination/Pagination.tsx index 3cfa45b07fc..96343ee4a1a 100644 --- a/studio/components/grid/components/footer/pagination/Pagination.tsx +++ b/studio/components/grid/components/footer/pagination/Pagination.tsx @@ -1,4 +1,4 @@ -import { FC, useState, useEffect, useRef } from 'react' +import { FC, useState, useEffect } from 'react' import AwesomeDebouncePromise from 'awesome-debounce-promise' import { Button, InputNumber, IconArrowRight, IconArrowLeft } from '@supabase/ui' import { DropdownControl, showConfirmAlert } from '../../common' @@ -31,8 +31,8 @@ const Pagination: FC = () => { if (state.page != page) setPage(state.page) }, [state.page, page]) - // [Joshen] Oddly the methods here seems to be getting stale state - console.log('Pagination', state.selectedRows) + // [Joshen] Oddly without this, state.selectedRows will be stale + useEffect(() => {}, [state.selectedRows]) const onPreviousPage = () => { if (state.page > 1) { diff --git a/studio/components/grid/components/grid/Grid.tsx b/studio/components/grid/components/grid/Grid.tsx index b9e774ac1ca..471ee85f128 100644 --- a/studio/components/grid/components/grid/Grid.tsx +++ b/studio/components/grid/components/grid/Grid.tsx @@ -11,20 +11,22 @@ function rowKeyGetter(row: SupaRow) { return row.idx } +interface IGrid extends GridProps { + rows: any[] +} + export const Grid = memo( - forwardRef( + forwardRef( ( - { width, height, containerClass, gridClass, rowClass }, + { width, height, containerClass, gridClass, rowClass, rows }, ref: React.Ref | undefined ) => { const dispatch = useDispatch() const state = useTrackedState() + // workaround to force state tracking on state.gridColumns const columnHeaders = state.gridColumns.map((x) => `${x.key}_${x.frozen}`) - const { gridColumns, rows, onError: onErrorFunc } = state - - // PROBLEM MIGHT BE HERE - MEMO IS NOT LETTING THE GRID RERENDER - console.log('Rows', rows) + const { gridColumns, onError: onErrorFunc } = state function onColumnResize(index: number, width: number) { updateColumnResizeDebounced(index, width, dispatch) @@ -43,19 +45,11 @@ export const Grid = memo( payload, }) }) - if (error) { - if (onErrorFunc) onErrorFunc(error) - } else { - // dispatch({ - // type: 'SET_ROWS', - // payload: { rows }, - // }) - } + if (error && onErrorFunc) onErrorFunc(error) } } function onSelectedRowsChange(selectedRows: ReadonlySet) { - console.log('onSelectedRowsChange', selectedRows) dispatch({ type: 'SELECTED_ROWS_CHANGE', payload: { selectedRows }, diff --git a/studio/components/grid/store/reducers/row.ts b/studio/components/grid/store/reducers/row.ts index 84033ecf1ca..e54ab2ca9d0 100644 --- a/studio/components/grid/store/reducers/row.ts +++ b/studio/components/grid/store/reducers/row.ts @@ -85,7 +85,6 @@ const RowReducer = (state: RowInitialState, action: ROW_ACTIONTYPE) => { } } case 'SET_ROWS': { - console.log('SET_ROWS', action.payload.rows[0]) return { ...state, rows: action.payload.rows, diff --git a/studio/components/grid/utils/row.ts b/studio/components/grid/utils/row.ts index 23e2f8943b7..1e60e115180 100644 --- a/studio/components/grid/utils/row.ts +++ b/studio/components/grid/utils/row.ts @@ -22,7 +22,6 @@ export async function fetchPage(state: InitialStateType, dispatch: (value: unkno state.filters, state.sorts ) - console.log('Fetch page', { page: state.page, rowsPerPage: state.rowsPerPage }, data, error) if (error) { // TODO: handle fetch rows data error } else { From d91cf6bd4c0012881e442e8a9400c66e207d7fef Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 21 Jun 2022 16:31:19 +0800 Subject: [PATCH 44/57] Disable pagination buttons while rows are being fetched --- .../grid/components/footer/pagination/Pagination.tsx | 11 ++++++++--- studio/components/grid/store/reducers/row.ts | 9 +++++++++ studio/components/grid/utils/row.ts | 2 ++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/studio/components/grid/components/footer/pagination/Pagination.tsx b/studio/components/grid/components/footer/pagination/Pagination.tsx index 96343ee4a1a..1c3ef9f5847 100644 --- a/studio/components/grid/components/footer/pagination/Pagination.tsx +++ b/studio/components/grid/components/footer/pagination/Pagination.tsx @@ -1,6 +1,6 @@ import { FC, useState, useEffect } from 'react' import AwesomeDebouncePromise from 'awesome-debounce-promise' -import { Button, InputNumber, IconArrowRight, IconArrowLeft } from '@supabase/ui' +import { Button, InputNumber, IconArrowRight, IconArrowLeft, IconLoader } from '@supabase/ui' import { DropdownControl, showConfirmAlert } from '../../common' import { useDispatch, useTrackedState } from '../../../store' @@ -34,6 +34,10 @@ const Pagination: FC = () => { // [Joshen] Oddly without this, state.selectedRows will be stale useEffect(() => {}, [state.selectedRows]) + // [Joshen] Note: I've made pagination buttons disabled while rows are being fetched for now + // at least until we can send an abort signal to cancel requests if users are mashing the + // pagination buttons to find the data they want + const onPreviousPage = () => { if (state.page > 1) { if (state.selectedRows.size >= 1) { @@ -106,7 +110,7 @@ const Pagination: FC = () => {

{`${state.totalRows.toLocaleString()} records`}

+ {state.isLoading && } )}
diff --git a/studio/components/grid/store/reducers/row.ts b/studio/components/grid/store/reducers/row.ts index e54ab2ca9d0..a3de4a47f9f 100644 --- a/studio/components/grid/store/reducers/row.ts +++ b/studio/components/grid/store/reducers/row.ts @@ -4,6 +4,7 @@ import { Dictionary, SupaRow } from '../../types' import { INIT_ACTIONTYPE } from './base' export interface RowInitialState { + isLoading: boolean rows: SupaRow[] selectedRows: ReadonlySet selectedCellPosition: { idx: number; rowIdx: number } | null @@ -13,6 +14,7 @@ export interface RowInitialState { } export const rowInitialState: RowInitialState = { + isLoading: false, rows: [], selectedRows: new Set(), selectedCellPosition: null, @@ -45,6 +47,7 @@ type ROW_ACTIONTYPE = | { type: 'ADD_NEW_ROW'; payload: Dictionary } | { type: 'EDIT_ROW'; payload: { row: Dictionary; idx: number } } | { type: 'REMOVE_ROWS'; payload: { rowIdxs: number[] } } + | { type: 'SET_IS_LOADING'; payload: { isLoading: boolean } } const RowReducer = (state: RowInitialState, action: ROW_ACTIONTYPE) => { switch (action.type) { @@ -131,6 +134,12 @@ const RowReducer = (state: RowInitialState, action: ROW_ACTIONTYPE) => { totalRows: totalRows, } } + case 'SET_IS_LOADING': { + return { + ...state, + isLoading: action.payload.isLoading, + } + } default: return state } diff --git a/studio/components/grid/utils/row.ts b/studio/components/grid/utils/row.ts index 1e60e115180..b0c45f8f270 100644 --- a/studio/components/grid/utils/row.ts +++ b/studio/components/grid/utils/row.ts @@ -16,6 +16,7 @@ export async function fetchCount(state: InitialStateType, dispatch: (value: unkn export async function fetchPage(state: InitialStateType, dispatch: (value: unknown) => void) { if (!state.rowService) return + dispatch({ type: 'SET_IS_LOADING', payload: { isLoading: true } }) const { data, error } = await state.rowService.fetchPage( state.page, state.rowsPerPage, @@ -30,5 +31,6 @@ export async function fetchPage(state: InitialStateType, dispatch: (value: unkno payload: { rows: data?.rows ?? [] }, }) } + dispatch({ type: 'SET_IS_LOADING', payload: { isLoading: false } }) } export const refreshPageDebounced = AwesomeDebouncePromise(fetchPage, 500) From 287926d802478b43c9fa54540e58dd54612ccc3a Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 21 Jun 2022 16:49:29 +0800 Subject: [PATCH 45/57] Add fix for spreadsheet import utils when infering boolean type column --- .../SpreadsheetImport/SpreadsheetImport.utils.ts | 2 +- .../components/Editor/SpreadsheetImport.utils.test.js | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.utils.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.utils.ts index 4ba1a2c91b3..508fa4e5db2 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.utils.ts +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.utils.ts @@ -123,7 +123,7 @@ export const inferColumnType = (column: string, rows: object[]) => { // Infer boolean type if (includes(['true', 'false'], columnData.toString().toLowerCase())) { const isAllBoolean = columnDataAcrossRows.every((item: any) => - includes(['true', 'false'], item.toString().toLowerCase()) + includes(['true', 'false'], (item || '').toString().toLowerCase()) ) if (isAllBoolean) { return 'boolean' diff --git a/studio/tests/components/Editor/SpreadsheetImport.utils.test.js b/studio/tests/components/Editor/SpreadsheetImport.utils.test.js index a9d49688477..230fc76df53 100644 --- a/studio/tests/components/Editor/SpreadsheetImport.utils.test.js +++ b/studio/tests/components/Editor/SpreadsheetImport.utils.test.js @@ -35,6 +35,15 @@ describe('SpreadsheedImport.utils: inferColumnType', () => { const type2 = inferColumnType('isRetired', mockData2) expect(type2).toBe('boolean') }) + test('should infer text type for a supposed boolean column if one of the rows has a null value', () => { + const mockData3 = [ + { name: 'bob', height: '161.72', isRetired: 'false' }, + { name: 'bob', height: '161.72', isRetired: 'true' }, + { name: 'bob', height: '161.72', isRetired: null }, + ] + const type3 = inferColumnType('isRetired', mockData3) + expect(type3).toBe('text') + }) test('should infer objects as jsonb types correctly', () => { const mockData = [{ name: 'bob', metadata: '{}' }] const type = inferColumnType('metadata', mockData) From 24928cc50c3a31b2dda6b01224acc70c35a7c918 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 21 Jun 2022 17:08:09 +0800 Subject: [PATCH 46/57] Update logic --- .../SpreadsheetImport/SpreadsheetImport.utils.ts | 7 ++++--- .../components/Editor/SpreadsheetImport.utils.test.js | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.utils.ts b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.utils.ts index 508fa4e5db2..ed3dca9ffcd 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.utils.ts +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.utils.ts @@ -122,9 +122,10 @@ export const inferColumnType = (column: string, rows: object[]) => { // Infer boolean type if (includes(['true', 'false'], columnData.toString().toLowerCase())) { - const isAllBoolean = columnDataAcrossRows.every((item: any) => - includes(['true', 'false'], (item || '').toString().toLowerCase()) - ) + const isAllBoolean = columnDataAcrossRows.every((item: any) => { + if (item === null) return true + else return includes(['true', 'false'], item.toString().toLowerCase()) + }) if (isAllBoolean) { return 'boolean' } diff --git a/studio/tests/components/Editor/SpreadsheetImport.utils.test.js b/studio/tests/components/Editor/SpreadsheetImport.utils.test.js index 230fc76df53..11ebfbe75c9 100644 --- a/studio/tests/components/Editor/SpreadsheetImport.utils.test.js +++ b/studio/tests/components/Editor/SpreadsheetImport.utils.test.js @@ -35,14 +35,14 @@ describe('SpreadsheedImport.utils: inferColumnType', () => { const type2 = inferColumnType('isRetired', mockData2) expect(type2).toBe('boolean') }) - test('should infer text type for a supposed boolean column if one of the rows has a null value', () => { + test('should infer boolean type for a supposed boolean column if one of the rows has a null value', () => { const mockData3 = [ { name: 'bob', height: '161.72', isRetired: 'false' }, { name: 'bob', height: '161.72', isRetired: 'true' }, { name: 'bob', height: '161.72', isRetired: null }, ] const type3 = inferColumnType('isRetired', mockData3) - expect(type3).toBe('text') + expect(type3).toBe('boolean') }) test('should infer objects as jsonb types correctly', () => { const mockData = [{ name: 'bob', metadata: '{}' }] From 0db4c90b295078469c8b37e5a52b2996cd39c986 Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Wed, 22 Jun 2022 01:45:36 +0800 Subject: [PATCH 47/57] Update index.tsx --- studio/pages/join/index.tsx | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/studio/pages/join/index.tsx b/studio/pages/join/index.tsx index c16000f9778..179d23a37af 100644 --- a/studio/pages/join/index.tsx +++ b/studio/pages/join/index.tsx @@ -9,12 +9,12 @@ import { get, post, delete_ } from 'lib/common/fetch' import { useEffect } from 'react' interface TokenInfoI { - organization_name: string | undefined - token_does_not_exist: boolean - email_match: boolean - authorized_user: boolean - expired_token: boolean - invite_id: number + organization_name?: string | undefined + token_does_not_exist?: boolean + email_match?: boolean + authorized_user?: boolean + expired_token?: boolean + invite_id?: number } type TokenInfo = TokenInfoI | undefined @@ -36,14 +36,22 @@ const User = () => { } = tokenValidationInfo || {} useEffect(() => { - async function fetchTokenInfo() { - try { - const response = await get(`${API_URL}/organizations/${slug}/members/join?token=${token}`) - setTokenValidationInfo(response) - setTokenInfoLoaded(true) - } catch (error) { - console.error(error) + const fetchTokenInfo = async () => { + const response = await get(`${API_URL}/organizations/${slug}/members/join?token=${token}`) + console.log('i got here') + + // if (!response.ok) { + // throw response + // } + + if (response.status == 401) { + setTokenValidationInfo({ + authorized_user: false, + }) } + + setTokenValidationInfo(response) + setTokenInfoLoaded(true) } if (router.query.token && !tokenInfoLoaded) { From 2b23eb7a51606ab5f682b4955d8f2ff35c90f593 Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Wed, 22 Jun 2022 01:55:35 +0800 Subject: [PATCH 48/57] Update index.tsx --- studio/pages/join/index.tsx | 61 ++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/studio/pages/join/index.tsx b/studio/pages/join/index.tsx index 179d23a37af..29ed30adf4a 100644 --- a/studio/pages/join/index.tsx +++ b/studio/pages/join/index.tsx @@ -38,20 +38,23 @@ const User = () => { useEffect(() => { const fetchTokenInfo = async () => { const response = await get(`${API_URL}/organizations/${slug}/members/join?token=${token}`) - console.log('i got here') + console.log(response) // if (!response.ok) { // throw response // } - if (response.status == 401) { + if (response.error && response.error.code === 401) { + console.log('401111') setTokenValidationInfo({ authorized_user: false, }) + } else { + setTokenInfoLoaded(true) + setTokenValidationInfo(response) } - setTokenValidationInfo(response) - setTokenInfoLoaded(true) + // console.log(response) } if (router.query.token && !tokenInfoLoaded) { @@ -93,6 +96,11 @@ const User = () => { } } + const isError = + !!(tokenInfoLoaded && token_does_not_exist) || + (tokenInfoLoaded && !email_match) || + (tokenInfoLoaded && expired_token) + return (
{ )}
-
+
{authorized_user && !expired_token && email_match && tokenInfoLoaded && (
@@ -154,12 +167,13 @@ const User = () => { )} {tokenInfoLoaded && ( -
- {(tokenInfoLoaded && token_does_not_exist) || - (tokenInfoLoaded && !email_match) || - (tokenInfoLoaded && expired_token && ( - - ))} +
+ {isError && } {tokenInfoLoaded && token_does_not_exist ? 'The invite token is invalid. Try copying and pasting the link from the invite email, or ask the organization owner to invite you again.' @@ -182,17 +196,20 @@ const User = () => {
)} {!authorized_user && ( -
- - - +
+

You will need to sign in first

+
+ + + +
)}
From f1bb239e68f543ce54ef266e5be04f221ddbd4b2 Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Wed, 22 Jun 2022 01:59:07 +0800 Subject: [PATCH 49/57] Update index.tsx --- studio/pages/join/index.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/studio/pages/join/index.tsx b/studio/pages/join/index.tsx index 29ed30adf4a..547c5a0737a 100644 --- a/studio/pages/join/index.tsx +++ b/studio/pages/join/index.tsx @@ -137,10 +137,9 @@ const User = () => {
{authorized_user && !expired_token && email_match && tokenInfoLoaded && ( @@ -170,7 +169,7 @@ const User = () => {
{isError && } From 163e519a5978d6c8100e97257ff633950de5c7f0 Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Wed, 22 Jun 2022 02:05:39 +0800 Subject: [PATCH 50/57] Update index.tsx --- studio/pages/join/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/studio/pages/join/index.tsx b/studio/pages/join/index.tsx index 547c5a0737a..0e24c5ce9fa 100644 --- a/studio/pages/join/index.tsx +++ b/studio/pages/join/index.tsx @@ -137,7 +137,7 @@ const User = () => {
@@ -169,7 +169,7 @@ const User = () => {
{isError && } From b7824519735f12476db32e1ed1d56deb33521b00 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Tue, 21 Jun 2022 17:12:02 -0230 Subject: [PATCH 51/57] Cleans up invite language --- studio/pages/join/index.tsx | 34 ++----- studio/pages/org/[slug]/join.tsx | 166 ------------------------------- 2 files changed, 8 insertions(+), 192 deletions(-) delete mode 100644 studio/pages/org/[slug]/join.tsx diff --git a/studio/pages/join/index.tsx b/studio/pages/join/index.tsx index 0e24c5ce9fa..efd51954be1 100644 --- a/studio/pages/join/index.tsx +++ b/studio/pages/join/index.tsx @@ -1,6 +1,6 @@ import React from 'react' -import { useStore, withAuth } from 'hooks' -import { Button, IconAlertCircle, Typography } from '@supabase/ui' +import { useStore } from 'hooks' +import { Button, IconAlertCircle } from '@supabase/ui' import { useState } from 'react' import { useRouter } from 'next/router' import Link from 'next/link' @@ -22,7 +22,8 @@ type TokenInfo = TokenInfoI | undefined const User = () => { const router = useRouter() const { slug, token } = router.query - const { ui } = useStore() + const { ui, app } = useStore() + const [isSubmitting, setIsSubmitting] = useState(false) const [tokenValidationInfo, setTokenValidationInfo] = useState(undefined) const [tokenInfoLoaded, setTokenInfoLoaded] = useState(false) @@ -38,14 +39,8 @@ const User = () => { useEffect(() => { const fetchTokenInfo = async () => { const response = await get(`${API_URL}/organizations/${slug}/members/join?token=${token}`) - console.log(response) - - // if (!response.ok) { - // throw response - // } - + console.log({response}) if (response.error && response.error.code === 401) { - console.log('401111') setTokenValidationInfo({ authorized_user: false, }) @@ -53,8 +48,6 @@ const User = () => { setTokenInfoLoaded(true) setTokenValidationInfo(response) } - - // console.log(response) } if (router.query.token && !tokenInfoLoaded) { @@ -73,7 +66,7 @@ const User = () => { setIsSubmitting(false) } else { setIsSubmitting(false) - // app.organizations.load() + app.organizations.load() router.push('/') } } @@ -143,7 +136,7 @@ const User = () => { >
{authorized_user && !expired_token && email_match && tokenInfoLoaded && ( -
+
)} - {authorized_user && !expired_token && email_match && tokenInfoLoaded && ( -
- - - -
- )} {!authorized_user && (

You will need to sign in first

diff --git a/studio/pages/org/[slug]/join.tsx b/studio/pages/org/[slug]/join.tsx deleted file mode 100644 index 3efcf2f8b8a..00000000000 --- a/studio/pages/org/[slug]/join.tsx +++ /dev/null @@ -1,166 +0,0 @@ -import React from 'react' -import { useStore, withAuth } from 'hooks' -import { Button, IconAlertCircle, Typography } from '@supabase/ui' -import { useState } from 'react' -import { useRouter } from 'next/router' -import Link from 'next/link' -import { API_URL } from 'lib/constants' -import { get, post, delete_ } from 'lib/common/fetch' -import { useEffect } from 'react' - -interface TokenInfo { - organization_name: string | undefined - token_does_not_exist: boolean - email_match: boolean - authorized_user: boolean - expired_token: boolean - invite_id: number -} - -const User = () => { - const router = useRouter() - const { slug, token } = router.query - const { app, ui } = useStore() - const [isSubmitting, setIsSubmitting] = useState(false) - const [tokenValidationInfo, setTokenValidationInfo] = useState() - const [tokenInfoLoaded, setTokenInfoLoaded] = useState(false) - const { - token_does_not_exist, - email_match, - authorized_user, - expired_token, - organization_name, - invite_id, - } = tokenValidationInfo || {} - - useEffect(() => { - let cancel = false - - async function fetchTokenInfo() { - const response = await get(`${API_URL}/organizations/${slug}/members/join?token=${token}`, {}) - if (!cancel) { - setTokenValidationInfo(response) - setTokenInfoLoaded(true) - } - } - - if (router.query.token) { - fetchTokenInfo() - } - - return () => { - cancel = true - } - }, []) - - async function handleJoinOrganization() { - setIsSubmitting(true) - const response = await post(`${API_URL}/organizations/${slug}/members/join?token=${token}`, {}) - if (response.error) { - ui.setNotification({ - category: 'error', - message: `Failed to join organization: ${response.error.message}`, - }) - setIsSubmitting(false) - } else { - setIsSubmitting(false) - app.organizations.load() - router.push('/') - } - } - - async function handleDeclineJoinOrganization() { - setIsSubmitting(true) - const response = await delete_( - `${API_URL}/organizations/${slug}/members/invite?invited_id=${invite_id}`, - {} - ) - if (response.error) { - ui.setNotification({ - category: 'error', - message: `Failed to decline invitation: ${response.error.message}`, - }) - setIsSubmitting(false) - } else { - setIsSubmitting(false) - router.push('/') - } - } - - return ( -
-
-
- - - Supabase - - Supabase - - - - -

- Join {organization_name && email_match ? organization_name : 'a new organization'} -

- - {!token_does_not_exist ? ( -

- You have been invited to join{' '} - {organization_name && email_match - ? `${organization_name}'s organization` - : 'a new organization'}{' '} - at Supabase. -

- ) : ( -
- )} -
- - {authorized_user && !expired_token && email_match && tokenInfoLoaded && ( -
- - - -
- )} - - {tokenInfoLoaded && ( -
- {token_does_not_exist || - !email_match || - (expired_token && )} - - {token_does_not_exist - ? 'The invite token is invalid. Try copying and pasting the link from the invite email, or ask the organization owner to invite you again.' - : !email_match - ? 'The email address does not match. Are you signed in with right GitHub account?' - : expired_token - ? 'The invite token has expired. Please request a new one from the organization owner.' - : ''} -
- )} -
-
- ) -} - -export default withAuth(User) From e144daaf6dc0a062d330f0da36674b759490a976 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Tue, 21 Jun 2022 21:32:52 -0230 Subject: [PATCH 52/57] Cleanup --- studio/pages/join/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/studio/pages/join/index.tsx b/studio/pages/join/index.tsx index efd51954be1..f0bf91b5d82 100644 --- a/studio/pages/join/index.tsx +++ b/studio/pages/join/index.tsx @@ -39,7 +39,6 @@ const User = () => { useEffect(() => { const fetchTokenInfo = async () => { const response = await get(`${API_URL}/organizations/${slug}/members/join?token=${token}`) - console.log({response}) if (response.error && response.error.code === 401) { setTokenValidationInfo({ authorized_user: false, @@ -109,7 +108,6 @@ const User = () => { alt="Supabase" className="block h-[24px] cursor-pointer rounded" /> - {/*

Supabase

*/}

From f88c504946d4b2c73c0fdba15ecb247a33320ec5 Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Wed, 22 Jun 2022 14:36:57 +0800 Subject: [PATCH 53/57] Update index.tsx --- studio/pages/join/index.tsx | 186 +++++++++++++++++++++++------------- 1 file changed, 121 insertions(+), 65 deletions(-) diff --git a/studio/pages/join/index.tsx b/studio/pages/join/index.tsx index f0bf91b5d82..7bc410273b5 100644 --- a/studio/pages/join/index.tsx +++ b/studio/pages/join/index.tsx @@ -1,12 +1,13 @@ import React from 'react' -import { useStore } from 'hooks' -import { Button, IconAlertCircle } from '@supabase/ui' +import { useProfile, useStore } from 'hooks' +import { Button, IconAlertCircle, IconCheck, IconCheckSquare, IconXSquare } from '@supabase/ui' import { useState } from 'react' import { useRouter } from 'next/router' import Link from 'next/link' import { API_URL } from 'lib/constants' import { get, post, delete_ } from 'lib/common/fetch' import { useEffect } from 'react' +import { auth } from 'lib/gotrue' interface TokenInfoI { organization_name?: string | undefined @@ -23,6 +24,7 @@ const User = () => { const router = useRouter() const { slug, token } = router.query const { ui, app } = useStore() + const { profile, isLoading } = useProfile() const [isSubmitting, setIsSubmitting] = useState(false) const [tokenValidationInfo, setTokenValidationInfo] = useState(undefined) @@ -36,8 +38,14 @@ const User = () => { invite_id, } = tokenValidationInfo || {} + const loginRedirectLink = `/?next=${encodeURIComponent( + `/join?token=${router.query.token}&slug=${router.query.slug}` + )}` + useEffect(() => { const fetchTokenInfo = async () => { + await ui.load() + const response = await get(`${API_URL}/organizations/${slug}/members/join?token=${token}`) if (response.error && response.error.code === 401) { setTokenValidationInfo({ @@ -93,37 +101,99 @@ const User = () => { (tokenInfoLoaded && !email_match) || (tokenInfoLoaded && expired_token) + const ErrorMessage = () => { + const Container = ({ children }: { children: React.ReactNode }) => ( +

+ {/* */} + <>{children} +
+ ) + + const message = token_does_not_exist ? ( + <> +

The invite token is invalid.

+

+ Try copying and pasting the link from the invite email, or ask the organization owner to + invite you again. +

+ + ) : !email_match ? ( + <> +

+ Your email address {profile?.primary_email} does not match the email address this + invitation was sent to. +

+

+ To accept this invitation, you will need to{' '} + { + await auth.signOut() + router.reload() + }} + > + sign out + {' '} + and then sign in or create a new account using the same email address used in the + invitation. +

+ + ) : expired_token ? ( + <> +

The invite token has expired.

+

Please request a new one from the organization member.

+ + ) : ( + '' + ) + + return {message} + } + return (
+ + + Supabase + +
-
- - - Supabase - - -

- Join {organization_name && email_match ? organization_name : 'a new organization'} -

+
+ {/*

Organization invitation

*/} {!token_does_not_exist ? ( -

- You have been invited to join{' '} - {organization_name && email_match - ? `${organization_name}'s organization` - : 'a new organization'}{' '} - at Supabase. -

+ <> +

You have been invited to join

+ {organization_name ? ( + <> +

+ {organization_name ? `${organization_name}` : 'an organization'}{' '} +

+

an organization on Supabase

+ + ) : ( + <> +

{'an organization'}

+ + )} + ) : ( -
+
)}
@@ -132,60 +202,46 @@ const User = () => { ' ' )} > -
+
{authorized_user && !expired_token && email_match && tokenInfoLoaded && ( -
+
+ - -
)} - {tokenInfoLoaded && ( -
- {isError && } + {tokenInfoLoaded && } - {tokenInfoLoaded && token_does_not_exist - ? 'The invite token is invalid. Try copying and pasting the link from the invite email, or ask the organization owner to invite you again.' - : tokenInfoLoaded && !email_match - ? 'The invite email address does not match your current Supabase email. Are you signed in with right GitHub account?' - : tokenInfoLoaded && expired_token - ? 'The invite token has expired. Please request a new one from the organization owner.' - : ''} -
- )} {!authorized_user && (
-

You will need to sign in first

-
- - + + +
From 3be2e23d6357d9fcc5a8a68786df4a8d1ec0d01d Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 22 Jun 2022 16:37:43 +0800 Subject: [PATCH 54/57] Update index.tsx --- studio/pages/join/index.tsx | 63 +++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/studio/pages/join/index.tsx b/studio/pages/join/index.tsx index 7bc410273b5..54b133b526b 100644 --- a/studio/pages/join/index.tsx +++ b/studio/pages/join/index.tsx @@ -1,6 +1,6 @@ import React from 'react' import { useProfile, useStore } from 'hooks' -import { Button, IconAlertCircle, IconCheck, IconCheckSquare, IconXSquare } from '@supabase/ui' +import { Button, IconCheckSquare } from '@supabase/ui' import { useState } from 'react' import { useRouter } from 'next/router' import Link from 'next/link' @@ -9,7 +9,7 @@ import { get, post, delete_ } from 'lib/common/fetch' import { useEffect } from 'react' import { auth } from 'lib/gotrue' -interface TokenInfoI { +interface ITokenInfo { organization_name?: string | undefined token_does_not_exist?: boolean email_match?: boolean @@ -18,13 +18,13 @@ interface TokenInfoI { invite_id?: number } -type TokenInfo = TokenInfoI | undefined +type TokenInfo = ITokenInfo | undefined -const User = () => { +const JoinOrganizationPage = () => { const router = useRouter() const { slug, token } = router.query const { ui, app } = useStore() - const { profile, isLoading } = useProfile() + const { profile } = useProfile() const [isSubmitting, setIsSubmitting] = useState(false) const [tokenValidationInfo, setTokenValidationInfo] = useState(undefined) @@ -38,9 +38,7 @@ const User = () => { invite_id, } = tokenValidationInfo || {} - const loginRedirectLink = `/?next=${encodeURIComponent( - `/join?token=${router.query.token}&slug=${router.query.slug}` - )}` + const loginRedirectLink = `/?next=${encodeURIComponent(`/join?token=${token}&slug=${slug}`)}` useEffect(() => { const fetchTokenInfo = async () => { @@ -57,10 +55,10 @@ const User = () => { } } - if (router.query.token && !tokenInfoLoaded) { + if (token && !tokenInfoLoaded) { fetchTokenInfo() } - }, [router.query]) + }, [token]) async function handleJoinOrganization() { setIsSubmitting(true) @@ -102,18 +100,6 @@ const User = () => { (tokenInfoLoaded && expired_token) const ErrorMessage = () => { - const Container = ({ children }: { children: React.ReactNode }) => ( -
- {/* */} - <>{children} -
- ) - const message = token_does_not_exist ? ( <>

The invite token is invalid.

@@ -152,11 +138,26 @@ const User = () => { '' ) - return {message} + return ( +
+ {message} +
+ ) } return ( -
+
{ className=" bg-scale-300 border-scale-400 mx-auto overflow-hidden rounded-md border text-center shadow - md:w-[380px] + md:w-[400px] " >
- {/*

Organization invitation

*/} - {!token_does_not_exist ? ( <>

You have been invited to join

@@ -205,13 +204,7 @@ const User = () => {
{authorized_user && !expired_token && email_match && tokenInfoLoaded && (
-