diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f6fe994 --- /dev/null +++ b/.gitignore @@ -0,0 +1,313 @@ +# Created by https://www.toptal.com/developers/gitignore/api/node,python +# Edit at https://www.toptal.com/developers/gitignore?templates=node,python +.coder +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +# End of https://www.toptal.com/developers/gitignore/api/node,python diff --git a/frontend/.gitignore b/frontend/.gitignore deleted file mode 100644 index 4cde958..0000000 --- a/frontend/.gitignore +++ /dev/null @@ -1,28 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -# Codetyper.nvim - AI coding partner files -*.coder.* -.coder/ diff --git a/frontend/src/api/qrcode.ts b/frontend/src/api/qrcode.ts new file mode 100644 index 0000000..40a8b11 --- /dev/null +++ b/frontend/src/api/qrcode.ts @@ -0,0 +1,27 @@ +import axios from 'axios'; +import type { GenerateResult } from '@types/qrcode'; + +export async function generateQr(payload: { text: string; size: number }): Promise { + const url = 'http://localhost:5000/api/qrcode/generate'; + const resp = await axios.post(url, payload, { + responseType: 'arraybuffer', + validateStatus: (s) => s >= 200 && s < 300, + }); + + const contentType = (resp.headers['content-type'] || '').toString(); + + // Try JSON parse in case backend returns { imageUrl } + try { + const decoded = new TextDecoder().decode(resp.data); + const parsed = JSON.parse(decoded); + if (parsed?.imageUrl) return { imageUrl: parsed.imageUrl }; + } catch (error) { + console.error(error); + } + + if (contentType.startsWith('image/')) { + return { mime: contentType, data: resp.data as ArrayBuffer }; + } + + return { mime: contentType || 'image/png', data: resp.data as ArrayBuffer }; +} diff --git a/frontend/src/components/organisms/QRForm.tsx b/frontend/src/components/organisms/QRForm.tsx index b1abb76..ff232d4 100644 --- a/frontend/src/components/organisms/QRForm.tsx +++ b/frontend/src/components/organisms/QRForm.tsx @@ -1,43 +1,40 @@ -import React, { useState } from 'react'; -import axios from 'axios'; +import React from 'react'; import styles from '@styles/QRForm.module.scss'; +import { useQrStore } from '@store/qrStore'; +import { generateQr } from '@api/qrcode'; +import type { GenerateResult } from '@types/qrcode'; const QRForm: React.FC = () => { - const [text, setText] = useState(''); - const [size, setSize] = useState<'150' | '300' | '600'>('300'); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const [imageUrl, setImageUrl] = useState(null); + const { state, dispatch } = useQrStore(); + const { text, size, loading } = state; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - setError(null); - setImageUrl(null); + dispatch({ type: 'setError', payload: null }); + dispatch({ type: 'setImageUrl', payload: null }); if (!text.trim()) { - setError('Please enter a URL or text to encode.'); + dispatch({ type: 'setError', payload: 'Please enter a URL or text to encode.' }); return; } - setLoading(true); + dispatch({ type: 'setLoading', payload: true }); try { - // Call a mock API to simulate server-side generation step. - // Using jsonplaceholder as a harmless mock endpoint. - await axios.post('https://jsonplaceholder.typicode.com/posts', { - payload: text, - size, - }); + const res: GenerateResult = await generateQr({ text, size }); - // Build a QR image URL using a public QR generator for preview purposes. - // In a real implementation the server would return the final image URL or binary data. - const generatedUrl = `https://api.qrserver.com/v1/create-qr-code/?size=${size}x${size}&data=${encodeURIComponent( - text - )}`; - setImageUrl(generatedUrl); - } catch (err) { - setError('Failed to generate QR code. Please try again.'); + if ('imageUrl' in res && res.imageUrl) { + dispatch({ type: 'setImageUrl', payload: res.imageUrl }); + } else if ('data' in res && res.data && res.mime) { + const blob = new Blob([res.data], { type: res.mime }); + const objectUrl = URL.createObjectURL(blob); + dispatch({ type: 'setImageUrl', payload: objectUrl }); + } else { + dispatch({ type: 'setError', payload: 'Unexpected server response' }); + } + } catch { + dispatch({ type: 'setError', payload: 'Failed to generate QR code. Please try again.' }); } finally { - setLoading(false); + dispatch({ type: 'setLoading', payload: false }); } }; @@ -48,15 +45,15 @@ const QRForm: React.FC = () => { setText(e.target.value)} + onChange={(e) => dispatch({ type: 'setText', payload: e.target.value })} placeholder="https://example.com or some text" className={styles.qrFormInput} /> - {error &&
{error}
} + {state.error &&
{state.error}
}