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