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.
Files changed (92) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +129 -0
  3. package/dist/chunk-2IGBGILB.js +1230 -0
  4. package/dist/chunk-2IGBGILB.js.map +1 -0
  5. package/dist/create-bin.js +17 -0
  6. package/dist/create-bin.js.map +1 -0
  7. package/dist/index.js +164 -0
  8. package/dist/index.js.map +1 -0
  9. package/modules/_core/api-client/package.json +22 -0
  10. package/modules/_core/api-client/src/index.ts +32 -0
  11. package/modules/_core/api-client/src/schema.d.ts +12 -0
  12. package/modules/_core/api-client/tsconfig.json +7 -0
  13. package/modules/_core/mobile/app/_layout.tsx +10 -0
  14. package/modules/_core/mobile/app/index.tsx +24 -0
  15. package/modules/_core/mobile/app.json +18 -0
  16. package/modules/_core/mobile/babel.config.js +6 -0
  17. package/modules/_core/mobile/expo-env.d.ts +3 -0
  18. package/modules/_core/mobile/jest.config.js +10 -0
  19. package/modules/_core/mobile/metro.config.js +18 -0
  20. package/modules/_core/mobile/package.json +36 -0
  21. package/modules/_core/mobile/src/nav.ts +9 -0
  22. package/modules/_core/mobile/src/providers.tsx +17 -0
  23. package/modules/_core/mobile/tsconfig.json +10 -0
  24. package/modules/_core/root/.editorconfig +15 -0
  25. package/modules/_core/root/_gitignore +29 -0
  26. package/modules/_core/server/.python-version +1 -0
  27. package/modules/_core/server/config/__init__.py +0 -0
  28. package/modules/_core/server/config/asgi.py +7 -0
  29. package/modules/_core/server/config/settings.py +127 -0
  30. package/modules/_core/server/config/urls.py +17 -0
  31. package/modules/_core/server/config/wsgi.py +7 -0
  32. package/modules/_core/server/manage.py +21 -0
  33. package/modules/_core/server/pyproject.toml +38 -0
  34. package/modules/_core/server/tests/test_health.py +4 -0
  35. package/modules/_core/shared/package.json +17 -0
  36. package/modules/_core/shared/src/index.ts +11 -0
  37. package/modules/_core/shared/tsconfig.json +7 -0
  38. package/modules/_core/web/app/globals.css +3 -0
  39. package/modules/_core/web/app/layout.tsx +19 -0
  40. package/modules/_core/web/app/page.tsx +21 -0
  41. package/modules/_core/web/app/providers.tsx +19 -0
  42. package/modules/_core/web/next-env.d.ts +5 -0
  43. package/modules/_core/web/next.config.mjs +9 -0
  44. package/modules/_core/web/package.json +31 -0
  45. package/modules/_core/web/postcss.config.mjs +6 -0
  46. package/modules/_core/web/src/nav.ts +9 -0
  47. package/modules/_core/web/tailwind.config.ts +9 -0
  48. package/modules/_core/web/tsconfig.json +18 -0
  49. package/modules/_core/web/vitest.config.mts +19 -0
  50. package/modules/auth/mobile/app/login.tsx +63 -0
  51. package/modules/auth/mobile/src/auth/auth-context.tsx +61 -0
  52. package/modules/auth/mobile/src/auth/client.test.ts +69 -0
  53. package/modules/auth/mobile/src/auth/client.ts +59 -0
  54. package/modules/auth/mobile/src/lib/config.ts +22 -0
  55. package/modules/auth/mobile/src/lib/token-store.ts +20 -0
  56. package/modules/auth/module.json +47 -0
  57. package/modules/auth/server/accounts/__init__.py +0 -0
  58. package/modules/auth/server/accounts/admin.py +4 -0
  59. package/modules/auth/server/accounts/apps.py +6 -0
  60. package/modules/auth/server/accounts/migrations/0001_initial.py +38 -0
  61. package/modules/auth/server/accounts/migrations/__init__.py +0 -0
  62. package/modules/auth/server/accounts/models.py +39 -0
  63. package/modules/auth/server/accounts/serializers.py +21 -0
  64. package/modules/auth/server/accounts/urls.py +11 -0
  65. package/modules/auth/server/accounts/views.py +26 -0
  66. package/modules/auth/server/tests/test_auth.py +44 -0
  67. package/modules/auth/shared/src/auth.ts +27 -0
  68. package/modules/auth/web/app/login/page.tsx +61 -0
  69. package/modules/auth/web/src/auth/auth-context.test.tsx +66 -0
  70. package/modules/auth/web/src/auth/auth-context.tsx +63 -0
  71. package/modules/auth/web/src/auth/client.test.ts +73 -0
  72. package/modules/auth/web/src/auth/client.ts +59 -0
  73. package/modules/auth/web/src/lib/config.ts +2 -0
  74. package/modules/auth/web/src/lib/token-store.ts +23 -0
  75. package/modules/ci/module.json +11 -0
  76. package/modules/db-postgres/module.json +22 -0
  77. package/modules/docker/module.json +19 -0
  78. package/modules/docker/root/infra/docker-compose.yml +20 -0
  79. package/modules/docker/server/.dockerignore +7 -0
  80. package/modules/example/mobile/app/profile.test.tsx +44 -0
  81. package/modules/example/mobile/app/profile.tsx +39 -0
  82. package/modules/example/module.json +21 -0
  83. package/modules/example/web/app/profile/page.test.tsx +41 -0
  84. package/modules/example/web/app/profile/page.tsx +34 -0
  85. package/modules/storage/module.json +34 -0
  86. package/modules/storage/server/storage/__init__.py +0 -0
  87. package/modules/storage/server/storage/base.py +24 -0
  88. package/modules/storage/server/storage/factory.py +20 -0
  89. package/modules/storage/server/storage/local.py +28 -0
  90. package/modules/storage/server/storage/s3.py +30 -0
  91. package/modules/storage/server/tests/test_storage.py +20 -0
  92. 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,9 @@
1
+ export interface NavLink {
2
+ href: string;
3
+ label: string;
4
+ }
5
+
6
+ /** Components register their screens here; the home screen lists them. */
7
+ export const navLinks: NavLink[] = [
8
+ // <partweave:routes>
9
+ ];
@@ -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,10 @@
1
+ {
2
+ "extends": "expo/tsconfig.base",
3
+ "compilerOptions": {
4
+ "strict": true,
5
+ "paths": {
6
+ "@/*": ["./src/*"]
7
+ }
8
+ },
9
+ "include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"]
10
+ }
@@ -0,0 +1,15 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ trim_trailing_whitespace = true
8
+ indent_style = space
9
+ indent_size = 2
10
+
11
+ [*.py]
12
+ indent_size = 4
13
+
14
+ [Makefile]
15
+ indent_style = tab
@@ -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,7 @@
1
+ import os
2
+
3
+ from django.core.asgi import get_asgi_application
4
+
5
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
6
+
7
+ application = get_asgi_application()
@@ -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,7 @@
1
+ import os
2
+
3
+ from django.core.wsgi import get_wsgi_application
4
+
5
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
6
+
7
+ application = get_wsgi_application()
@@ -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,4 @@
1
+ def test_health(client):
2
+ resp = client.get("/api/health")
3
+ assert resp.status_code == 200
4
+ assert resp.json() == {"status": "ok"}
@@ -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,7 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "noEmit": true
5
+ },
6
+ "include": ["src"]
7
+ }
@@ -0,0 +1,3 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
@@ -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,5 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+
4
+ // NOTE: This file should not be edited
5
+ // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -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,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
@@ -0,0 +1,9 @@
1
+ export interface NavLink {
2
+ href: string;
3
+ label: string;
4
+ }
5
+
6
+ /** Components register their pages here; the home page lists them. */
7
+ export const navLinks: NavLink[] = [
8
+ // <partweave:routes>
9
+ ];
@@ -0,0 +1,9 @@
1
+ import type { Config } from "tailwindcss";
2
+
3
+ export default {
4
+ content: ["./app/**/*.{ts,tsx}", "./src/**/*.{ts,tsx}"],
5
+ theme: {
6
+ extend: {},
7
+ },
8
+ plugins: [],
9
+ } satisfies Config;
@@ -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
+ });