diff --git a/client/.gitignore b/client/.gitignore index 1eab1a2..a547bf3 100644 --- a/client/.gitignore +++ b/client/.gitignore @@ -1,46 +1,24 @@ -# Miscellaneous -*.class +# Logs +logs *.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ -migrate_working_dir/ - -# 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/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* -# Web related +node_modules +dist +dist-ssr +*.local -# 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 +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/client/.metadata b/client/.metadata deleted file mode 100644 index eaf47da..0000000 --- a/client/.metadata +++ /dev/null @@ -1,30 +0,0 @@ -# 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: "bae5e49bc2a867403c43b2aae2de8f8c33b037e4" - channel: "stable" - -project_type: app - -# Tracks metadata for the flutter migrate command -migration: - platforms: - - platform: root - create_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4 - base_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4 - - platform: web - create_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4 - base_revision: bae5e49bc2a867403c43b2aae2de8f8c33b037e4 - - # User provided section - - # List of Local paths (relative to this file) that should be - # ignored by the migrate tool. - # - # Files that are not part of the templates will be ignored by default. - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/client/.vscode/launch.json b/client/.vscode/launch.json deleted file mode 100644 index 611d181..0000000 --- a/client/.vscode/launch.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "client", - "request": "launch", - "type": "dart" - }, - { - "name": "client (profile mode)", - "request": "launch", - "type": "dart", - "flutterMode": "profile" - }, - { - "name": "client (release mode)", - "request": "launch", - "type": "dart", - "flutterMode": "release" - } - ] -} \ No newline at end of file diff --git a/client/Dockerfile b/client/Dockerfile index e276203..6c8dd66 100644 --- a/client/Dockerfile +++ b/client/Dockerfile @@ -1,27 +1,16 @@ -ARG ARCH=linux/amd64 - -FROM --platform=${ARCH} dart:3.3 - -ARG API_AUTHORITY - -RUN apt-get update && apt-get install -y unzip wget xz-utils - -WORKDIR /usr - -RUN git clone https://github.com/flutter/flutter.git /usr/local/flutter -ENV PATH "$PATH:/usr/local/flutter/bin:/usr/local/flutter/bin/cache/dart-sdk/bin" - -# Install webdev -RUN flutter pub global activate webdev - -# Set the PATH environment variable for the webdev executable -ENV PATH "$PATH:/root/.pub-cache/bin" +FROM node:18-alpine as build WORKDIR /app - +COPY package.json ./ +COPY yarn.lock ./ +RUN yarn --frozen-lockfile COPY . . +RUN yarn build -RUN flutter pub get - -# Build the Flutter application for the web -RUN flutter build web --release --dart-define API_AUTHORITY=${API_AUTHORITY} +FROM node:18-alpine +WORKDIR /app +ENV NODE_ENV=production +COPY package.json ./ +COPY yarn.lock ./ +COPY --from=build /app/dist ./dist/ +RUN ls -a diff --git a/client/README.md b/client/README.md deleted file mode 100644 index 4e6072f..0000000 --- a/client/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# toiki - -A new Flutter project. - -## Getting Started - -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) - -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. diff --git a/client/analysis_options.yaml b/client/analysis_options.yaml deleted file mode 100644 index 03ea3cc..0000000 --- a/client/analysis_options.yaml +++ /dev/null @@ -1,7 +0,0 @@ -include: package:flutter_lints/flutter.yaml - -linter: - rules: - avoid_print: true - prefer_single_quotes: true - always_use_package_imports: true diff --git a/client/eslint.config.js b/client/eslint.config.js new file mode 100644 index 0000000..092408a --- /dev/null +++ b/client/eslint.config.js @@ -0,0 +1,28 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +) diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..7705fe4 --- /dev/null +++ b/client/index.html @@ -0,0 +1,18 @@ + + + + + Toiki + + + + + + + + + +
+ + + diff --git a/client/lib/blocs/conversion/conversion_bloc.dart b/client/lib/blocs/conversion/conversion_bloc.dart deleted file mode 100644 index 2c321ae..0000000 --- a/client/lib/blocs/conversion/conversion_bloc.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:toiki/blocs/conversion/conversion_event.dart'; -import 'package:toiki/blocs/conversion/conversion_state.dart'; -import 'package:toiki/repositories/conversion_repository.dart'; - -class ConversionBloc extends Bloc { - ConversionBloc({required this.conversionRepository}) - : super(InitialConversionState()) { - on(_convertUrl); - } - - final ConversionRepository conversionRepository; - - Future _convertUrl( - ConvertUrlConversionEvent event, - Emitter emit, - ) async { - try { - emit(ConvertUrlLoadingState()); - - final res = await conversionRepository.convertUrl(url: event.url); - - emit(ConvertUrlDoneState(url: res.data.url)); - } catch (e) { - emit(ConvertUrlErrorState()); - } - } -} diff --git a/client/lib/blocs/conversion/conversion_event.dart b/client/lib/blocs/conversion/conversion_event.dart deleted file mode 100644 index 9d6ef88..0000000 --- a/client/lib/blocs/conversion/conversion_event.dart +++ /dev/null @@ -1,7 +0,0 @@ -class ConversionEvent {} - -class ConvertUrlConversionEvent extends ConversionEvent { - ConvertUrlConversionEvent({required this.url}); - - final String url; -} diff --git a/client/lib/blocs/conversion/conversion_state.dart b/client/lib/blocs/conversion/conversion_state.dart deleted file mode 100644 index 607b5c9..0000000 --- a/client/lib/blocs/conversion/conversion_state.dart +++ /dev/null @@ -1,15 +0,0 @@ -class ConversionState {} - -class InitialConversionState extends ConversionState {} - -// ConvertUrl - -class ConvertUrlLoadingState extends ConversionState {} - -class ConvertUrlDoneState extends ConversionState { - ConvertUrlDoneState({required this.url}); - - final String url; -} - -class ConvertUrlErrorState extends ConversionState {} diff --git a/client/lib/core/app.dart b/client/lib/core/app.dart deleted file mode 100644 index 1f52ed2..0000000 --- a/client/lib/core/app.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:toiki/core/bloc_providers.dart'; -import 'package:toiki/core/repository_providers.dart'; -import 'package:toiki/screens/main_screen.dart'; -import 'package:toiki/utils/constants.dart'; - -class App extends StatelessWidget { - const App({ - super.key, - }); - - @override - Widget build(BuildContext context) { - return RepositoryProviders( - child: BlocProviders( - child: MaterialApp( - title: Constants.appName, - debugShowCheckedModeBanner: false, - theme: ThemeData(primarySwatch: Colors.red), - routes: { - MainScreen.route: (context) => MainScreen(), - }, - ), - ), - ); - } -} diff --git a/client/lib/core/bloc_providers.dart b/client/lib/core/bloc_providers.dart deleted file mode 100644 index 7919cd1..0000000 --- a/client/lib/core/bloc_providers.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:toiki/blocs/conversion/conversion_bloc.dart'; -import 'package:toiki/repositories/conversion_repository.dart'; - -class BlocProviders extends StatelessWidget { - const BlocProviders({ - super.key, - required this.child, - }); - - final Widget child; - - @override - Widget build(BuildContext context) { - return MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => ConversionBloc( - conversionRepository: - RepositoryProvider.of(context), - ), - ) - ], - child: child, - ); - } -} diff --git a/client/lib/core/repository_providers.dart b/client/lib/core/repository_providers.dart deleted file mode 100644 index d9b697f..0000000 --- a/client/lib/core/repository_providers.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:toiki/repositories/conversion_repository.dart'; -import 'package:toiki/utils/api.dart'; - -class RepositoryProviders extends StatelessWidget { - const RepositoryProviders({ - super.key, - required this.child, - }); - - final Widget child; - - @override - Widget build(BuildContext context) { - final api = Api(); - - return MultiRepositoryProvider( - providers: [ - RepositoryProvider(create: (context) => ConversionRepository(api: api)), - ], - child: child, - ); - } -} diff --git a/client/lib/exceptions/http_bad_request_exception.dart b/client/lib/exceptions/http_bad_request_exception.dart deleted file mode 100644 index 53a33c1..0000000 --- a/client/lib/exceptions/http_bad_request_exception.dart +++ /dev/null @@ -1,10 +0,0 @@ -class HttpBadRequestException implements Exception { - const HttpBadRequestException({required this.message}); - - final String? message; - - @override - String toString() { - return '$runtimeType ($message)'; - } -} diff --git a/client/lib/exceptions/http_entity_not_found_exception.dart b/client/lib/exceptions/http_entity_not_found_exception.dart deleted file mode 100644 index 5d8a6ed..0000000 --- a/client/lib/exceptions/http_entity_not_found_exception.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:toiki/exceptions/http_bad_request_exception.dart'; - -class HttpEntityNotFoundException extends HttpBadRequestException { - const HttpEntityNotFoundException({ - super.message, - }); -} diff --git a/client/lib/exceptions/http_missing_authorization_exception.dart b/client/lib/exceptions/http_missing_authorization_exception.dart deleted file mode 100644 index 7a0658f..0000000 --- a/client/lib/exceptions/http_missing_authorization_exception.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:toiki/exceptions/http_bad_request_exception.dart'; - -class HttpMissingAuthorizationException extends HttpBadRequestException { - const HttpMissingAuthorizationException({ - super.message, - }); -} diff --git a/client/lib/main.dart b/client/lib/main.dart deleted file mode 100644 index efc7fd4..0000000 --- a/client/lib/main.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:toiki/core/app.dart'; - -void main() { - runApp(const App()); -} diff --git a/client/lib/models/convert_url_response.dart b/client/lib/models/convert_url_response.dart deleted file mode 100644 index 61662bd..0000000 --- a/client/lib/models/convert_url_response.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; - -part 'convert_url_response.g.dart'; - -@JsonSerializable(fieldRename: FieldRename.none, explicitToJson: true) -class ConvertUrlResponse { - ConvertUrlResponse({ - required this.data, - }); - - final ConvertUrlResponseData data; - - factory ConvertUrlResponse.fromJson(Map json) => - _$ConvertUrlResponseFromJson(json); - - Map toJson() => _$ConvertUrlResponseToJson(this); -} - -@JsonSerializable(fieldRename: FieldRename.none, explicitToJson: true) -class ConvertUrlResponseData { - ConvertUrlResponseData({ - required this.url, - }); - - final String url; - - factory ConvertUrlResponseData.fromJson(Map json) => - _$ConvertUrlResponseDataFromJson(json); - - Map toJson() => _$ConvertUrlResponseDataToJson(this); -} diff --git a/client/lib/models/convert_url_response.g.dart b/client/lib/models/convert_url_response.g.dart deleted file mode 100644 index b62b54f..0000000 --- a/client/lib/models/convert_url_response.g.dart +++ /dev/null @@ -1,30 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'convert_url_response.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -ConvertUrlResponse _$ConvertUrlResponseFromJson(Map json) => - ConvertUrlResponse( - data: - ConvertUrlResponseData.fromJson(json['data'] as Map), - ); - -Map _$ConvertUrlResponseToJson(ConvertUrlResponse instance) => - { - 'data': instance.data.toJson(), - }; - -ConvertUrlResponseData _$ConvertUrlResponseDataFromJson( - Map json) => - ConvertUrlResponseData( - url: json['url'] as String, - ); - -Map _$ConvertUrlResponseDataToJson( - ConvertUrlResponseData instance) => - { - 'url': instance.url, - }; diff --git a/client/lib/modules/.gitkeep b/client/lib/modules/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/client/lib/repositories/conversion_repository.dart b/client/lib/repositories/conversion_repository.dart deleted file mode 100644 index f83125f..0000000 --- a/client/lib/repositories/conversion_repository.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:toiki/models/convert_url_response.dart'; -import 'package:toiki/utils/api.dart'; - -class ConversionRepository { - ConversionRepository({required this.api}); - - final Api api; - - Future convertUrl({required String url}) async { - final res = await api.get( - path: '/api/convert', - queryParameters: { - 'url': url, - }, - ); - - return ConvertUrlResponse.fromJson(res); - } -} diff --git a/client/lib/screens/main_screen.dart b/client/lib/screens/main_screen.dart deleted file mode 100644 index 8fd2986..0000000 --- a/client/lib/screens/main_screen.dart +++ /dev/null @@ -1,124 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:toiki/blocs/conversion/conversion_bloc.dart'; -import 'package:toiki/blocs/conversion/conversion_event.dart'; -import 'package:toiki/blocs/conversion/conversion_state.dart'; -import 'package:toiki/utils/constants.dart'; -import 'package:url_launcher/url_launcher.dart'; - -class MainScreen extends StatelessWidget { - MainScreen({ - super.key, - }); - - static const route = '/'; - - final _focusNode = FocusNode(); - final _controller = TextEditingController(); - - Future _copy({ - required String text, - required BuildContext context, - }) async { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('YouTube url copied to clipboard.')), - ); - - await Clipboard.setData(ClipboardData(text: text)); - } - - Widget _getContent(BuildContext context) { - return BlocConsumer( - listener: (context, state) { - if (state is ConvertUrlDoneState) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Url converted.')), - ); - } else if (state is ConvertUrlErrorState) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Something went wrong.')), - ); - } - }, - builder: (context, state) { - final isLoading = state is ConvertUrlLoadingState; - - return Padding( - padding: const EdgeInsets.all(32), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Center( - child: ConstrainedBox( - constraints: BoxConstraints.loose(const Size.fromWidth(480)), - child: TextFormField( - enabled: !isLoading, - focusNode: _focusNode, - controller: _controller, - decoration: const InputDecoration( - labelText: 'Url', - alignLabelWithHint: true, - ), - onFieldSubmitted: (value) { - BlocProvider.of(context) - .add(ConvertUrlConversionEvent(url: value)); - }, - ), - ), - ), - Builder( - builder: (context) { - if (isLoading) { - return const Padding( - padding: EdgeInsets.only(top: 64), - child: Center( - child: SizedBox( - width: 24, - height: 24, - child: CircularProgressIndicator(), - ), - ), - ); - } - - if (state is ConvertUrlDoneState) { - return Padding( - padding: const EdgeInsets.only(top: 64), - child: Column(children: [ - InkWell( - onTap: () => launchUrl(Uri.parse(state.url)), - child: Text(state.url), - ), - Center( - child: IconButton( - tooltip: 'Copy', - icon: const Icon(Icons.copy), - onPressed: () { - _copy(text: state.url, context: context); - }, - ), - ), - ]), - ); - } - - return Container(); - }, - ), - ], - ), - ); - }, - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text(Constants.appName)), - body: _getContent(context), - ); - } -} diff --git a/client/lib/utils/api.dart b/client/lib/utils/api.dart deleted file mode 100644 index ce35412..0000000 --- a/client/lib/utils/api.dart +++ /dev/null @@ -1,59 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:flutter/foundation.dart'; -import 'package:http/http.dart' as http; -import 'package:toiki/exceptions/http_bad_request_exception.dart'; -import 'package:toiki/exceptions/http_entity_not_found_exception.dart'; -import 'package:toiki/exceptions/http_missing_authorization_exception.dart'; -import 'package:toiki/utils/env.dart'; - -class Api { - final _client = http.Client(); - - Uri Function(String, String, [Map?]) get _getUri { - return kDebugMode ? Uri.http : Uri.https; - } - - Future> get({ - required String path, - Map? queryParameters, - }) async { - final request = _client.get( - _getUri(Env.apiAuthority, path, queryParameters), - ); - - final response = await request; - - await _validateResponseCode(request); - - return jsonDecode(utf8.decode(response.bodyBytes)) as Map; - } - - Future _validateResponseCode(FutureOr request) async { - final awaitedReq = await request; - - if (kDebugMode) { - print(awaitedReq.request.toString()); - print(awaitedReq.body); - } - - String? message; - - try { - final map = jsonDecode(utf8.decode(awaitedReq.bodyBytes)); - - message = map['message']; - } catch (e) { - if (kDebugMode) print(e); - } - - if ([401, 403].contains(awaitedReq.statusCode)) { - throw HttpMissingAuthorizationException(message: message); - } else if (awaitedReq.statusCode == 404) { - throw HttpEntityNotFoundException(message: message); - } else if (awaitedReq.statusCode >= 400 && awaitedReq.statusCode < 600) { - throw HttpBadRequestException(message: message); - } - } -} diff --git a/client/lib/utils/constants.dart b/client/lib/utils/constants.dart deleted file mode 100644 index 6d16d69..0000000 --- a/client/lib/utils/constants.dart +++ /dev/null @@ -1,3 +0,0 @@ -class Constants { - static const appName = 'Toiki'; -} diff --git a/client/lib/utils/env.dart b/client/lib/utils/env.dart deleted file mode 100644 index 82db105..0000000 --- a/client/lib/utils/env.dart +++ /dev/null @@ -1,4 +0,0 @@ -class Env { - static const apiAuthority = - String.fromEnvironment('API_AUTHORITY', defaultValue: 'localhost:8000'); -} diff --git a/client/lib/widgets/.gitkeep b/client/lib/widgets/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..14e0e50 --- /dev/null +++ b/client/package.json @@ -0,0 +1,32 @@ +{ + "name": "client", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@heroicons/react": "^2.2.0", + "@tailwindcss/vite": "^4.0.4", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "tailwindcss": "^4.0.4" + }, + "devDependencies": { + "@eslint/js": "^9.19.0", + "@types/react": "^19.0.8", + "@types/react-dom": "^19.0.3", + "@vitejs/plugin-react-swc": "^3.5.0", + "eslint": "^9.19.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.18", + "globals": "^15.14.0", + "typescript": "~5.7.2", + "typescript-eslint": "^8.22.0", + "vite": "^6.1.0" + } +} diff --git a/client/public/android-chrome-192x192.png b/client/public/android-chrome-192x192.png new file mode 100644 index 0000000..8a99c47 Binary files /dev/null and b/client/public/android-chrome-192x192.png differ diff --git a/client/public/android-chrome-512x512.png b/client/public/android-chrome-512x512.png new file mode 100644 index 0000000..56fdd78 Binary files /dev/null and b/client/public/android-chrome-512x512.png differ diff --git a/client/public/apple-touch-icon.png b/client/public/apple-touch-icon.png new file mode 100644 index 0000000..919e6b1 Binary files /dev/null and b/client/public/apple-touch-icon.png differ diff --git a/client/public/favicon-16x16.png b/client/public/favicon-16x16.png new file mode 100644 index 0000000..a1efa74 Binary files /dev/null and b/client/public/favicon-16x16.png differ diff --git a/client/public/favicon-32x32.png b/client/public/favicon-32x32.png new file mode 100644 index 0000000..e760b42 Binary files /dev/null and b/client/public/favicon-32x32.png differ diff --git a/client/public/favicon.ico b/client/public/favicon.ico new file mode 100644 index 0000000..e26a2e8 Binary files /dev/null and b/client/public/favicon.ico differ diff --git a/client/public/site.webmanifest b/client/public/site.webmanifest new file mode 100644 index 0000000..fa99de7 --- /dev/null +++ b/client/public/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/client/public/toiki_logo.webp b/client/public/toiki_logo.webp new file mode 100644 index 0000000..ecb5c0a Binary files /dev/null and b/client/public/toiki_logo.webp differ diff --git a/client/pubspec.lock b/client/pubspec.lock deleted file mode 100644 index cfc9387..0000000 --- a/client/pubspec.lock +++ /dev/null @@ -1,682 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _fe_analyzer_shared: - dependency: transitive - description: - name: _fe_analyzer_shared - sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" - url: "https://pub.dev" - source: hosted - version: "67.0.0" - analyzer: - dependency: transitive - description: - name: analyzer - sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" - url: "https://pub.dev" - source: hosted - version: "6.4.1" - args: - dependency: transitive - description: - name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 - url: "https://pub.dev" - source: hosted - version: "2.4.2" - async: - dependency: transitive - description: - name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" - url: "https://pub.dev" - source: hosted - version: "2.11.0" - bloc: - dependency: transitive - description: - name: bloc - sha256: f53a110e3b48dcd78136c10daa5d51512443cea5e1348c9d80a320095fa2db9e - url: "https://pub.dev" - source: hosted - version: "8.1.3" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - build: - dependency: transitive - description: - name: build - sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - build_config: - dependency: transitive - description: - name: build_config - sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 - url: "https://pub.dev" - source: hosted - version: "1.1.1" - build_daemon: - dependency: transitive - description: - name: build_daemon - sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" - url: "https://pub.dev" - source: hosted - version: "4.0.1" - build_resolvers: - dependency: transitive - description: - name: build_resolvers - sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" - url: "https://pub.dev" - source: hosted - version: "2.4.2" - build_runner: - dependency: "direct dev" - description: - name: build_runner - sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" - url: "https://pub.dev" - source: hosted - version: "2.4.8" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" - url: "https://pub.dev" - source: hosted - version: "7.3.0" - built_collection: - dependency: transitive - description: - name: built_collection - sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" - url: "https://pub.dev" - source: hosted - version: "5.1.1" - built_value: - dependency: transitive - description: - name: built_value - sha256: a3ec2e0f967bc47f69f95009bb93db936288d61d5343b9436e378b28a2f830c6 - url: "https://pub.dev" - source: hosted - version: "8.9.0" - characters: - dependency: transitive - description: - name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" - url: "https://pub.dev" - source: hosted - version: "1.3.0" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff - url: "https://pub.dev" - source: hosted - version: "2.0.3" - clock: - dependency: transitive - description: - name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf - url: "https://pub.dev" - source: hosted - version: "1.1.1" - code_builder: - dependency: transitive - description: - name: code_builder - sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 - url: "https://pub.dev" - source: hosted - version: "4.10.0" - collection: - dependency: transitive - description: - name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a - url: "https://pub.dev" - source: hosted - version: "1.18.0" - convert: - dependency: transitive - description: - name: convert - sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" - url: "https://pub.dev" - source: hosted - version: "3.1.1" - crypto: - dependency: transitive - description: - name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab - url: "https://pub.dev" - source: hosted - version: "3.0.3" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d - url: "https://pub.dev" - source: hosted - version: "1.0.6" - dart_style: - dependency: transitive - description: - name: dart_style - sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368" - url: "https://pub.dev" - source: hosted - version: "2.3.4" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" - url: "https://pub.dev" - source: hosted - version: "1.3.1" - file: - dependency: transitive - description: - name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" - url: "https://pub.dev" - source: hosted - version: "7.0.0" - fixnum: - dependency: transitive - description: - name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_bloc: - dependency: "direct main" - description: - name: flutter_bloc - sha256: "87325da1ac757fcc4813e6b34ed5dd61169973871fdf181d6c2109dd6935ece1" - url: "https://pub.dev" - source: hosted - version: "8.1.4" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 - url: "https://pub.dev" - source: hosted - version: "3.0.1" - 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" - frontend_server_client: - dependency: transitive - description: - name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" - url: "https://pub.dev" - source: hosted - version: "3.2.0" - glob: - dependency: transitive - description: - name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - graphs: - dependency: transitive - description: - name: graphs - sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 - url: "https://pub.dev" - source: hosted - version: "2.3.1" - http: - dependency: "direct main" - description: - name: http - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" - url: "https://pub.dev" - source: hosted - version: "1.2.1" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" - url: "https://pub.dev" - source: hosted - version: "3.2.1" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" - url: "https://pub.dev" - source: hosted - version: "4.0.2" - io: - dependency: transitive - description: - name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" - url: "https://pub.dev" - source: hosted - version: "1.0.4" - js: - dependency: transitive - description: - name: js - sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf - url: "https://pub.dev" - source: hosted - version: "0.7.1" - json_annotation: - dependency: "direct main" - description: - name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 - url: "https://pub.dev" - source: hosted - version: "4.8.1" - json_serializable: - dependency: "direct dev" - description: - name: json_serializable - sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969 - url: "https://pub.dev" - source: hosted - version: "6.7.1" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" - url: "https://pub.dev" - source: hosted - version: "10.0.0" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 - url: "https://pub.dev" - source: hosted - version: "2.0.1" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 - url: "https://pub.dev" - source: hosted - version: "2.0.1" - lints: - dependency: transitive - description: - name: lints - sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 - url: "https://pub.dev" - source: hosted - version: "3.0.0" - logging: - dependency: transitive - description: - name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - matcher: - dependency: transitive - description: - name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb - url: "https://pub.dev" - source: hosted - version: "0.12.16+1" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" - url: "https://pub.dev" - source: hosted - version: "0.8.0" - meta: - dependency: transitive - description: - name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 - url: "https://pub.dev" - source: hosted - version: "1.11.0" - mime: - dependency: transitive - description: - name: mime - sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" - url: "https://pub.dev" - source: hosted - version: "1.0.5" - nested: - dependency: transitive - description: - name: nested - sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - package_config: - dependency: transitive - description: - name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - path: - dependency: transitive - description: - name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" - url: "https://pub.dev" - source: hosted - version: "1.9.0" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" - source: hosted - version: "2.1.8" - pool: - dependency: transitive - description: - name: pool - sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" - url: "https://pub.dev" - source: hosted - version: "1.5.1" - provider: - dependency: transitive - description: - name: provider - sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" - url: "https://pub.dev" - source: hosted - version: "6.1.1" - pub_semver: - dependency: transitive - description: - name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 - url: "https://pub.dev" - source: hosted - version: "1.2.3" - shelf: - dependency: transitive - description: - name: shelf - sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 - url: "https://pub.dev" - source: hosted - version: "1.4.1" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" - url: "https://pub.dev" - source: hosted - version: "1.0.4" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - source_gen: - dependency: transitive - description: - name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" - url: "https://pub.dev" - source: hosted - version: "1.5.0" - source_helper: - dependency: transitive - description: - name: source_helper - sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" - url: "https://pub.dev" - source: hosted - version: "1.3.4" - source_span: - dependency: transitive - description: - name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" - url: "https://pub.dev" - source: hosted - version: "1.10.0" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" - url: "https://pub.dev" - source: hosted - version: "1.11.1" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 - url: "https://pub.dev" - source: hosted - version: "2.1.2" - stream_transform: - dependency: transitive - description: - name: stream_transform - sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 - url: "https://pub.dev" - source: hosted - version: "1.2.1" - test_api: - dependency: transitive - description: - name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" - url: "https://pub.dev" - source: hosted - version: "0.6.1" - timing: - dependency: transitive - description: - name: timing - sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" - url: "https://pub.dev" - source: hosted - version: "1.0.1" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c - url: "https://pub.dev" - source: hosted - version: "1.3.2" - url_launcher: - dependency: "direct main" - description: - name: url_launcher - sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c - url: "https://pub.dev" - source: hosted - version: "6.2.4" - url_launcher_android: - dependency: transitive - description: - name: url_launcher_android - sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745 - url: "https://pub.dev" - source: hosted - version: "6.3.0" - url_launcher_ios: - dependency: transitive - description: - name: url_launcher_ios - sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" - url: "https://pub.dev" - source: hosted - version: "6.2.4" - url_launcher_linux: - dependency: transitive - description: - name: url_launcher_linux - sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 - url: "https://pub.dev" - source: hosted - version: "3.1.1" - url_launcher_macos: - dependency: transitive - description: - name: url_launcher_macos - sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 - url: "https://pub.dev" - source: hosted - version: "3.1.0" - url_launcher_platform_interface: - dependency: transitive - description: - name: url_launcher_platform_interface - sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" - url: "https://pub.dev" - source: hosted - version: "2.3.2" - url_launcher_web: - dependency: transitive - description: - name: url_launcher_web - sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" - url: "https://pub.dev" - source: hosted - version: "2.2.0" - url_launcher_windows: - dependency: transitive - description: - name: url_launcher_windows - sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 - url: "https://pub.dev" - source: hosted - version: "3.1.1" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 - url: "https://pub.dev" - source: hosted - version: "13.0.0" - watcher: - dependency: transitive - description: - name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - web: - dependency: transitive - description: - name: web - sha256: "1d9158c616048c38f712a6646e317a3426da10e884447626167240d45209cbad" - url: "https://pub.dev" - source: hosted - version: "0.5.0" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - sha256: "1d8e795e2a8b3730c41b8a98a2dff2e0fb57ae6f0764a1c46ec5915387d257b2" - url: "https://pub.dev" - source: hosted - version: "2.4.4" - yaml: - dependency: transitive - description: - name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" - url: "https://pub.dev" - source: hosted - version: "3.1.2" -sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.13.0" diff --git a/client/pubspec.yaml b/client/pubspec.yaml deleted file mode 100644 index 7b23f8c..0000000 --- a/client/pubspec.yaml +++ /dev/null @@ -1,30 +0,0 @@ -name: toiki -description: Toiki - -publish_to: 'none' - -version: 1.0.0+1 - -environment: - sdk: '>=3.3.0 <4.0.0' - -dependencies: - flutter: - sdk: flutter - - cupertino_icons: ^1.0.6 - flutter_bloc: ^8.1.1 - json_annotation: ^4.7.0 - http: ^1.2.1 - url_launcher: ^6.2.4 - -dev_dependencies: - flutter_test: - sdk: flutter - - flutter_lints: ^3.0.0 - build_runner: ^2.3.2 - json_serializable: ^6.5.4 - -flutter: - uses-material-design: true diff --git a/client/src/App.tsx b/client/src/App.tsx new file mode 100644 index 0000000..9c9ed06 --- /dev/null +++ b/client/src/App.tsx @@ -0,0 +1,148 @@ +import { ArrowPathIcon, ClipboardIcon } from '@heroicons/react/20/solid'; +import { useState } from 'react'; +import { isYoutubeOrSpotify } from './utils/isYoutubeOrSpotify'; + +export function App() { + const [loading, setLoading] = useState(false); + const [result, setResult] = useState(null); + const [error, setError] = useState(''); + + const onSubmit = async (e: React.FormEvent) => { + setLoading(true); + e.preventDefault(); + const form = e.currentTarget; + const input = form.elements.item(0) as HTMLInputElement; + const formData = new FormData(form); + const url = formData.get('url'); + + if (!(url && typeof url === 'string')) { + input.setCustomValidity('Invalid URL'); + return; + } + + const match = isYoutubeOrSpotify(url); + + if (!match) { + input.setCustomValidity('Invalid URL'); + return; + } + + try { + const res = await fetch( + import.meta.env.VITE_API_URL + '/api/convert?url=' + url, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + } + ); + const data = (await res.json()) as { + data: { url: string; embedUrl: string }; + }; + setResult(data.data); + setLoading(false); + } catch (err) { + console.error(err); + setError('An error occurred, please try again.'); + } + }; + + return ( +
+
+ +
+
+
+ { + const input = e.target; + const value = input.value; + const match = isYoutubeOrSpotify(value); + if (!match) { + input.setCustomValidity('Invalid URL'); + } else { + input.setCustomValidity(''); + } + }} + /> +
+ +
+
+
+ {error &&

{error}

} + {result && ( +
+ Your URL is ready! + +
+ +
+