partweave 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +129 -0
- package/dist/chunk-2IGBGILB.js +1230 -0
- package/dist/chunk-2IGBGILB.js.map +1 -0
- package/dist/create-bin.js +17 -0
- package/dist/create-bin.js.map +1 -0
- package/dist/index.js +164 -0
- package/dist/index.js.map +1 -0
- package/modules/_core/api-client/package.json +22 -0
- package/modules/_core/api-client/src/index.ts +32 -0
- package/modules/_core/api-client/src/schema.d.ts +12 -0
- package/modules/_core/api-client/tsconfig.json +7 -0
- package/modules/_core/mobile/app/_layout.tsx +10 -0
- package/modules/_core/mobile/app/index.tsx +24 -0
- package/modules/_core/mobile/app.json +18 -0
- package/modules/_core/mobile/babel.config.js +6 -0
- package/modules/_core/mobile/expo-env.d.ts +3 -0
- package/modules/_core/mobile/jest.config.js +10 -0
- package/modules/_core/mobile/metro.config.js +18 -0
- package/modules/_core/mobile/package.json +36 -0
- package/modules/_core/mobile/src/nav.ts +9 -0
- package/modules/_core/mobile/src/providers.tsx +17 -0
- package/modules/_core/mobile/tsconfig.json +10 -0
- package/modules/_core/root/.editorconfig +15 -0
- package/modules/_core/root/_gitignore +29 -0
- package/modules/_core/server/.python-version +1 -0
- package/modules/_core/server/config/__init__.py +0 -0
- package/modules/_core/server/config/asgi.py +7 -0
- package/modules/_core/server/config/settings.py +127 -0
- package/modules/_core/server/config/urls.py +17 -0
- package/modules/_core/server/config/wsgi.py +7 -0
- package/modules/_core/server/manage.py +21 -0
- package/modules/_core/server/pyproject.toml +38 -0
- package/modules/_core/server/tests/test_health.py +4 -0
- package/modules/_core/shared/package.json +17 -0
- package/modules/_core/shared/src/index.ts +11 -0
- package/modules/_core/shared/tsconfig.json +7 -0
- package/modules/_core/web/app/globals.css +3 -0
- package/modules/_core/web/app/layout.tsx +19 -0
- package/modules/_core/web/app/page.tsx +21 -0
- package/modules/_core/web/app/providers.tsx +19 -0
- package/modules/_core/web/next-env.d.ts +5 -0
- package/modules/_core/web/next.config.mjs +9 -0
- package/modules/_core/web/package.json +31 -0
- package/modules/_core/web/postcss.config.mjs +6 -0
- package/modules/_core/web/src/nav.ts +9 -0
- package/modules/_core/web/tailwind.config.ts +9 -0
- package/modules/_core/web/tsconfig.json +18 -0
- package/modules/_core/web/vitest.config.mts +19 -0
- package/modules/auth/mobile/app/login.tsx +63 -0
- package/modules/auth/mobile/src/auth/auth-context.tsx +61 -0
- package/modules/auth/mobile/src/auth/client.test.ts +69 -0
- package/modules/auth/mobile/src/auth/client.ts +59 -0
- package/modules/auth/mobile/src/lib/config.ts +22 -0
- package/modules/auth/mobile/src/lib/token-store.ts +20 -0
- package/modules/auth/module.json +47 -0
- package/modules/auth/server/accounts/__init__.py +0 -0
- package/modules/auth/server/accounts/admin.py +4 -0
- package/modules/auth/server/accounts/apps.py +6 -0
- package/modules/auth/server/accounts/migrations/0001_initial.py +38 -0
- package/modules/auth/server/accounts/migrations/__init__.py +0 -0
- package/modules/auth/server/accounts/models.py +39 -0
- package/modules/auth/server/accounts/serializers.py +21 -0
- package/modules/auth/server/accounts/urls.py +11 -0
- package/modules/auth/server/accounts/views.py +26 -0
- package/modules/auth/server/tests/test_auth.py +44 -0
- package/modules/auth/shared/src/auth.ts +27 -0
- package/modules/auth/web/app/login/page.tsx +61 -0
- package/modules/auth/web/src/auth/auth-context.test.tsx +66 -0
- package/modules/auth/web/src/auth/auth-context.tsx +63 -0
- package/modules/auth/web/src/auth/client.test.ts +73 -0
- package/modules/auth/web/src/auth/client.ts +59 -0
- package/modules/auth/web/src/lib/config.ts +2 -0
- package/modules/auth/web/src/lib/token-store.ts +23 -0
- package/modules/ci/module.json +11 -0
- package/modules/db-postgres/module.json +22 -0
- package/modules/docker/module.json +19 -0
- package/modules/docker/root/infra/docker-compose.yml +20 -0
- package/modules/docker/server/.dockerignore +7 -0
- package/modules/example/mobile/app/profile.test.tsx +44 -0
- package/modules/example/mobile/app/profile.tsx +39 -0
- package/modules/example/module.json +21 -0
- package/modules/example/web/app/profile/page.test.tsx +41 -0
- package/modules/example/web/app/profile/page.tsx +34 -0
- package/modules/storage/module.json +34 -0
- package/modules/storage/server/storage/__init__.py +0 -0
- package/modules/storage/server/storage/base.py +24 -0
- package/modules/storage/server/storage/factory.py +20 -0
- package/modules/storage/server/storage/local.py +28 -0
- package/modules/storage/server/storage/s3.py +30 -0
- package/modules/storage/server/tests/test_storage.py +20 -0
- package/package.json +69 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mobile",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"main": "expo-router/entry",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "expo start",
|
|
8
|
+
"android": "expo start --android",
|
|
9
|
+
"ios": "expo start --ios",
|
|
10
|
+
"typecheck": "tsc --noEmit",
|
|
11
|
+
"test": "jest"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@expo/metro-runtime": "~6.1.2",
|
|
15
|
+
"expo": "~54.0.10",
|
|
16
|
+
"expo-constants": "~18.0.13",
|
|
17
|
+
"expo-linking": "~8.0.12",
|
|
18
|
+
"expo-router": "~6.0.24",
|
|
19
|
+
"expo-status-bar": "~3.0.9",
|
|
20
|
+
"react": "19.1.0",
|
|
21
|
+
"react-dom": "19.1.0",
|
|
22
|
+
"react-native": "0.81.5",
|
|
23
|
+
"react-native-safe-area-context": "~5.6.0",
|
|
24
|
+
"react-native-screens": "~4.16.0",
|
|
25
|
+
"react-native-web": "^0.21.2"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@babel/core": "^7.25.2",
|
|
29
|
+
"@testing-library/react-native": "^13.2.0",
|
|
30
|
+
"@types/jest": "^29.5.14",
|
|
31
|
+
"@types/react": "~19.1.10",
|
|
32
|
+
"jest": "^29.7.0",
|
|
33
|
+
"jest-expo": "~54.0.0",
|
|
34
|
+
"typescript": "~5.9.2"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ComponentType, ReactNode } from "react";
|
|
2
|
+
// <partweave:providers-import>
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Context providers, composed outside-in. Components register their provider
|
|
6
|
+
* at the anchor below; the base scaffold ships with none.
|
|
7
|
+
*/
|
|
8
|
+
const providers: ComponentType<{ children: ReactNode }>[] = [
|
|
9
|
+
// <partweave:providers>
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export function Providers({ children }: { children: ReactNode }) {
|
|
13
|
+
return providers.reduceRight(
|
|
14
|
+
(acc, Wrap) => <Wrap>{acc}</Wrap>,
|
|
15
|
+
children as ReactNode,
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# deps
|
|
2
|
+
node_modules/
|
|
3
|
+
.pnpm-store/
|
|
4
|
+
|
|
5
|
+
# builds
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
.next/
|
|
9
|
+
.expo/
|
|
10
|
+
*.tsbuildinfo
|
|
11
|
+
|
|
12
|
+
# python
|
|
13
|
+
__pycache__/
|
|
14
|
+
*.pyc
|
|
15
|
+
.venv/
|
|
16
|
+
venv/
|
|
17
|
+
*.sqlite3
|
|
18
|
+
|
|
19
|
+
# env
|
|
20
|
+
.env
|
|
21
|
+
.env.local
|
|
22
|
+
.env.*.local
|
|
23
|
+
|
|
24
|
+
# os / editor
|
|
25
|
+
.DS_Store
|
|
26
|
+
*.log
|
|
27
|
+
.idea/
|
|
28
|
+
.vscode/*
|
|
29
|
+
!.vscode/extensions.json
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
File without changes
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django settings for {{projectName}}.
|
|
3
|
+
|
|
4
|
+
Feature modules extend this file only at the `# <partweave:...>` anchors below, so
|
|
5
|
+
the base scaffold stays clean and re-generation is deterministic.
|
|
6
|
+
"""
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import environ
|
|
10
|
+
|
|
11
|
+
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
12
|
+
|
|
13
|
+
env = environ.Env(DJANGO_DEBUG=(bool, True))
|
|
14
|
+
# Load env from the server dir first, then the monorepo root (if present).
|
|
15
|
+
environ.Env.read_env(BASE_DIR / ".env")
|
|
16
|
+
environ.Env.read_env(BASE_DIR.parent.parent / ".env")
|
|
17
|
+
|
|
18
|
+
DEBUG = env.bool("DJANGO_DEBUG", default=True)
|
|
19
|
+
|
|
20
|
+
# SECRET_KEY signs sessions and (with the `auth` component) JWTs. The scaffolder
|
|
21
|
+
# writes a unique, random key into this project's .env at creation time, so no
|
|
22
|
+
# two projects — and nothing public — ever share a signing key. There is
|
|
23
|
+
# deliberately no shared production default: outside DEBUG a missing key raises
|
|
24
|
+
# ImproperlyConfigured rather than falling back to a globally-known value an
|
|
25
|
+
# attacker could use to forge tokens.
|
|
26
|
+
if DEBUG:
|
|
27
|
+
SECRET_KEY = env(
|
|
28
|
+
"DJANGO_SECRET_KEY",
|
|
29
|
+
default="django-insecure-dev-only-do-not-use-in-production",
|
|
30
|
+
)
|
|
31
|
+
else:
|
|
32
|
+
SECRET_KEY = env("DJANGO_SECRET_KEY")
|
|
33
|
+
# In DEBUG, allow any host so a phone/simulator can reach the dev server over the
|
|
34
|
+
# LAN. In production set DJANGO_DEBUG=false and provide DJANGO_ALLOWED_HOSTS.
|
|
35
|
+
ALLOWED_HOSTS = env.list(
|
|
36
|
+
"DJANGO_ALLOWED_HOSTS",
|
|
37
|
+
default=["*"] if DEBUG else ["localhost", "127.0.0.1"],
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
INSTALLED_APPS = [
|
|
41
|
+
"django.contrib.admin",
|
|
42
|
+
"django.contrib.auth",
|
|
43
|
+
"django.contrib.contenttypes",
|
|
44
|
+
"django.contrib.sessions",
|
|
45
|
+
"django.contrib.messages",
|
|
46
|
+
"django.contrib.staticfiles",
|
|
47
|
+
"rest_framework",
|
|
48
|
+
"drf_spectacular",
|
|
49
|
+
"corsheaders",
|
|
50
|
+
# <partweave:installed-apps>
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
MIDDLEWARE = [
|
|
54
|
+
"corsheaders.middleware.CorsMiddleware",
|
|
55
|
+
"django.middleware.security.SecurityMiddleware",
|
|
56
|
+
"django.contrib.sessions.middleware.SessionMiddleware",
|
|
57
|
+
"django.middleware.common.CommonMiddleware",
|
|
58
|
+
"django.middleware.csrf.CsrfViewMiddleware",
|
|
59
|
+
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
|
60
|
+
"django.contrib.messages.middleware.MessageMiddleware",
|
|
61
|
+
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
ROOT_URLCONF = "config.urls"
|
|
65
|
+
|
|
66
|
+
TEMPLATES = [
|
|
67
|
+
{
|
|
68
|
+
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
|
69
|
+
"DIRS": [],
|
|
70
|
+
"APP_DIRS": True,
|
|
71
|
+
"OPTIONS": {
|
|
72
|
+
"context_processors": [
|
|
73
|
+
"django.template.context_processors.request",
|
|
74
|
+
"django.contrib.auth.context_processors.auth",
|
|
75
|
+
"django.contrib.messages.context_processors.messages",
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
WSGI_APPLICATION = "config.wsgi.application"
|
|
82
|
+
|
|
83
|
+
# Defaults to SQLite so the server boots with zero infra. The `db-postgres`
|
|
84
|
+
# component sets DATABASE_URL to a Postgres DSN (and adds the driver).
|
|
85
|
+
DATABASES = {
|
|
86
|
+
"default": env.db(
|
|
87
|
+
"DATABASE_URL", default=f"sqlite:///{BASE_DIR / 'db.sqlite3'}"
|
|
88
|
+
),
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
AUTH_PASSWORD_VALIDATORS = [
|
|
92
|
+
{"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
|
|
93
|
+
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
|
|
94
|
+
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
|
|
95
|
+
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
LANGUAGE_CODE = "en-us"
|
|
99
|
+
TIME_ZONE = "UTC"
|
|
100
|
+
USE_I18N = True
|
|
101
|
+
USE_TZ = True
|
|
102
|
+
|
|
103
|
+
STATIC_URL = "static/"
|
|
104
|
+
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
|
105
|
+
|
|
106
|
+
REST_FRAMEWORK = {
|
|
107
|
+
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
|
|
108
|
+
"DEFAULT_AUTHENTICATION_CLASSES": [
|
|
109
|
+
# <partweave:drf-auth>
|
|
110
|
+
],
|
|
111
|
+
"DEFAULT_PERMISSION_CLASSES": [
|
|
112
|
+
"rest_framework.permissions.AllowAny",
|
|
113
|
+
],
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
SPECTACULAR_SETTINGS = {
|
|
117
|
+
"TITLE": "{{projectName}} API",
|
|
118
|
+
"VERSION": "0.1.0",
|
|
119
|
+
"SERVE_INCLUDE_SCHEMA": False,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# Allow all origins in DEBUG so local web/mobile clients can call the API. In
|
|
123
|
+
# production (DEBUG off) list the exact web origins allowed, via the environment.
|
|
124
|
+
CORS_ALLOW_ALL_ORIGINS = DEBUG
|
|
125
|
+
CORS_ALLOWED_ORIGINS = env.list("DJANGO_CORS_ALLOWED_ORIGINS", default=[])
|
|
126
|
+
|
|
127
|
+
# <partweave:settings>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from django.contrib import admin
|
|
2
|
+
from django.http import HttpRequest, JsonResponse
|
|
3
|
+
from django.urls import include, path # noqa: F401 (include used by modules)
|
|
4
|
+
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def health(_request: HttpRequest) -> JsonResponse:
|
|
8
|
+
return JsonResponse({"status": "ok"})
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
urlpatterns = [
|
|
12
|
+
path("admin/", admin.site.urls),
|
|
13
|
+
path("api/health", health, name="health"),
|
|
14
|
+
path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
|
|
15
|
+
path("api/docs/", SpectacularSwaggerView.as_view(url_name="schema"), name="docs"),
|
|
16
|
+
# <partweave:urls>
|
|
17
|
+
]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""Django's command-line utility for administrative tasks."""
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def main() -> None:
|
|
8
|
+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
|
|
9
|
+
try:
|
|
10
|
+
from django.core.management import execute_from_command_line
|
|
11
|
+
except ImportError as exc: # pragma: no cover
|
|
12
|
+
raise ImportError(
|
|
13
|
+
"Couldn't import Django. Are you sure it's installed and "
|
|
14
|
+
"available on your PYTHONPATH? Did you forget to activate a "
|
|
15
|
+
"virtual environment?"
|
|
16
|
+
) from exc
|
|
17
|
+
execute_from_command_line(sys.argv)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
main()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "server"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "{{projectName}} API"
|
|
5
|
+
requires-python = ">=3.12"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"django>=5.0,<6",
|
|
8
|
+
"djangorestframework>=3.15",
|
|
9
|
+
"drf-spectacular>=0.27",
|
|
10
|
+
"django-environ>=0.11",
|
|
11
|
+
"django-cors-headers>=4.4",
|
|
12
|
+
"gunicorn>=22.0",
|
|
13
|
+
# <partweave:deps>
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[dependency-groups]
|
|
17
|
+
dev = [
|
|
18
|
+
"pytest>=8.0",
|
|
19
|
+
"pytest-django>=4.8",
|
|
20
|
+
"ruff>=0.6",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
# This is an application, not a distributable package — no build backend needed.
|
|
24
|
+
[tool.uv]
|
|
25
|
+
package = false
|
|
26
|
+
|
|
27
|
+
[tool.ruff]
|
|
28
|
+
line-length = 100
|
|
29
|
+
target-version = "py312"
|
|
30
|
+
# Migrations are auto-generated; don't lint them.
|
|
31
|
+
extend-exclude = ["*/migrations/*"]
|
|
32
|
+
|
|
33
|
+
[tool.ruff.lint]
|
|
34
|
+
select = ["E", "F", "I", "UP", "DJ"]
|
|
35
|
+
|
|
36
|
+
[tool.pytest.ini_options]
|
|
37
|
+
DJANGO_SETTINGS_MODULE = "config.settings"
|
|
38
|
+
python_files = ["test_*.py", "*_test.py", "tests.py"]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@app/shared",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"types": "./src/index.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.ts"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"typecheck": "tsc --noEmit"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"typescript": "^5.7.2"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared TypeScript contracts for {{projectName}}.
|
|
3
|
+
*
|
|
4
|
+
* Components add their interfaces under `src/` and export them at the anchor
|
|
5
|
+
* below. Web and mobile depend on these interfaces, not on concrete impls, so
|
|
6
|
+
* implementations stay swappable per platform.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// <partweave:exports>
|
|
10
|
+
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import "./globals.css";
|
|
4
|
+
import { Providers } from "./providers";
|
|
5
|
+
|
|
6
|
+
export const metadata: Metadata = {
|
|
7
|
+
title: "{{projectName}}",
|
|
8
|
+
description: "{{description}}",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
12
|
+
return (
|
|
13
|
+
<html lang="en">
|
|
14
|
+
<body>
|
|
15
|
+
<Providers>{children}</Providers>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { navLinks } from "@/nav";
|
|
2
|
+
|
|
3
|
+
export default function Home() {
|
|
4
|
+
return (
|
|
5
|
+
<main className="mx-auto max-w-xl p-8">
|
|
6
|
+
<h1 className="text-2xl font-bold">{{projectName}}</h1>
|
|
7
|
+
<p className="mt-2 text-gray-600">Generated with base — start building.</p>
|
|
8
|
+
{navLinks.length > 0 && (
|
|
9
|
+
<ul className="mt-6 space-y-2">
|
|
10
|
+
{navLinks.map((l) => (
|
|
11
|
+
<li key={l.href}>
|
|
12
|
+
<a className="text-blue-600 underline" href={l.href}>
|
|
13
|
+
{l.label}
|
|
14
|
+
</a>
|
|
15
|
+
</li>
|
|
16
|
+
))}
|
|
17
|
+
</ul>
|
|
18
|
+
)}
|
|
19
|
+
</main>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ComponentType, ReactNode } from "react";
|
|
4
|
+
// <partweave:providers-import>
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Context providers, composed outside-in. Components register their provider
|
|
8
|
+
* at the anchor below; the base scaffold ships with none.
|
|
9
|
+
*/
|
|
10
|
+
const providers: ComponentType<{ children: ReactNode }>[] = [
|
|
11
|
+
// <partweave:providers>
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
export function Providers({ children }: { children: ReactNode }) {
|
|
15
|
+
return providers.reduceRight(
|
|
16
|
+
(acc, Wrap) => <Wrap>{acc}</Wrap>,
|
|
17
|
+
children as ReactNode,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** @type {import('next').NextConfig} */
|
|
2
|
+
const nextConfig = {
|
|
3
|
+
reactStrictMode: true,
|
|
4
|
+
// Workspace packages ship as TypeScript source, so Next transpiles them.
|
|
5
|
+
// (Harmless if a package isn't present in this project.)
|
|
6
|
+
transpilePackages: ["@app/shared", "@app/api-client"],
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default nextConfig;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "web",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"typecheck": "tsc --noEmit",
|
|
10
|
+
"lint": "next lint",
|
|
11
|
+
"test": "vitest run"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"next": "^15.1.0",
|
|
15
|
+
"react": "19.1.0",
|
|
16
|
+
"react-dom": "19.1.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/node": "^22.10.2",
|
|
20
|
+
"@testing-library/react": "^16.1.0",
|
|
21
|
+
"@types/react": "~19.1.10",
|
|
22
|
+
"@types/react-dom": "~19.1.0",
|
|
23
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
24
|
+
"autoprefixer": "^10.4.20",
|
|
25
|
+
"jsdom": "^25.0.1",
|
|
26
|
+
"postcss": "^8.4.49",
|
|
27
|
+
"tailwindcss": "^3.4.17",
|
|
28
|
+
"typescript": "^5.7.2",
|
|
29
|
+
"vitest": "^2.1.8"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"lib": ["dom", "dom.iterable", "ES2023"],
|
|
5
|
+
"jsx": "preserve",
|
|
6
|
+
"module": "esnext",
|
|
7
|
+
"moduleResolution": "Bundler",
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"incremental": true,
|
|
10
|
+
"allowJs": true,
|
|
11
|
+
"plugins": [{ "name": "next" }],
|
|
12
|
+
"paths": {
|
|
13
|
+
"@/*": ["./src/*"]
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
17
|
+
"exclude": ["node_modules"]
|
|
18
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import react from "@vitejs/plugin-react";
|
|
3
|
+
import { defineConfig } from "vitest/config";
|
|
4
|
+
|
|
5
|
+
// Runs `*.test.ts(x)` files that modules ship alongside their source. `@/…`
|
|
6
|
+
// resolves to ./src (same as the tsconfig path). Default env is node (fast, and
|
|
7
|
+
// `fetch`/`Response` are native); component tests opt into a DOM with a
|
|
8
|
+
// `// @vitest-environment jsdom` docblock at the top of the file.
|
|
9
|
+
export default defineConfig({
|
|
10
|
+
plugins: [react()],
|
|
11
|
+
resolve: {
|
|
12
|
+
alias: { "@": fileURLToPath(new URL("./src", import.meta.url)) },
|
|
13
|
+
},
|
|
14
|
+
test: {
|
|
15
|
+
environment: "node",
|
|
16
|
+
clearMocks: true,
|
|
17
|
+
include: ["**/*.test.ts", "**/*.test.tsx"],
|
|
18
|
+
},
|
|
19
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { useRouter } from "expo-router";
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
Text,
|
|
7
|
+
TextInput,
|
|
8
|
+
View,
|
|
9
|
+
} from "react-native";
|
|
10
|
+
import { useAuth } from "@/auth/auth-context";
|
|
11
|
+
|
|
12
|
+
export default function Login() {
|
|
13
|
+
const { login, register } = useAuth();
|
|
14
|
+
const router = useRouter();
|
|
15
|
+
const [email, setEmail] = useState("");
|
|
16
|
+
const [password, setPassword] = useState("");
|
|
17
|
+
const [isRegister, setIsRegister] = useState(false);
|
|
18
|
+
const [error, setError] = useState<string | null>(null);
|
|
19
|
+
|
|
20
|
+
async function onSubmit() {
|
|
21
|
+
setError(null);
|
|
22
|
+
try {
|
|
23
|
+
await (isRegister ? register : login)({ email, password });
|
|
24
|
+
router.replace("/");
|
|
25
|
+
} catch {
|
|
26
|
+
setError("Authentication failed");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<View style={styles.container}>
|
|
32
|
+
<Text style={styles.title}>{isRegister ? "Create account" : "Log in"}</Text>
|
|
33
|
+
<TextInput
|
|
34
|
+
style={styles.input}
|
|
35
|
+
placeholder="Email"
|
|
36
|
+
autoCapitalize="none"
|
|
37
|
+
keyboardType="email-address"
|
|
38
|
+
value={email}
|
|
39
|
+
onChangeText={setEmail}
|
|
40
|
+
/>
|
|
41
|
+
<TextInput
|
|
42
|
+
style={styles.input}
|
|
43
|
+
placeholder="Password"
|
|
44
|
+
secureTextEntry
|
|
45
|
+
value={password}
|
|
46
|
+
onChangeText={setPassword}
|
|
47
|
+
/>
|
|
48
|
+
{error ? <Text style={styles.error}>{error}</Text> : null}
|
|
49
|
+
<Button title={isRegister ? "Sign up" : "Log in"} onPress={onSubmit} />
|
|
50
|
+
<Text style={styles.toggle} onPress={() => setIsRegister(!isRegister)}>
|
|
51
|
+
{isRegister ? "Have an account? Log in" : "Need an account? Sign up"}
|
|
52
|
+
</Text>
|
|
53
|
+
</View>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const styles = StyleSheet.create({
|
|
58
|
+
container: { flex: 1, justifyContent: "center", padding: 24, gap: 12 },
|
|
59
|
+
title: { fontSize: 24, fontWeight: "bold" },
|
|
60
|
+
input: { borderWidth: 1, borderColor: "#ccc", borderRadius: 6, padding: 10 },
|
|
61
|
+
error: { color: "#dc2626" },
|
|
62
|
+
toggle: { color: "#2563eb", marginTop: 8 },
|
|
63
|
+
});
|