Lint and format
This commit is contained in:
parent
00c844e8d5
commit
7a0a5548f9
110 changed files with 1079 additions and 1086 deletions
|
|
@ -39,10 +39,16 @@ pnpm build
|
|||
|
||||
# Build only apps (main, external)
|
||||
pnpm build:apps
|
||||
|
||||
# Build main app for specific environments
|
||||
pnpm build:staging # Build for staging
|
||||
pnpm build:prod # Build for production
|
||||
```
|
||||
|
||||
**Note:** The `@xtablo/shared` and `@xtablo/ui` packages are source-only packages. They export TypeScript source files directly and are consumed by app bundlers (Vite) without a separate build step. This is faster and simpler for development.
|
||||
|
||||
**Environment Builds:** The main app supports environment-specific builds (`staging`, `production`) that are properly cached by Turborepo based on environment-specific inputs (`.env.staging`, `.env.production`, etc.).
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
|
|
@ -138,6 +144,22 @@ turbo build --filter='./packages/*'
|
|||
turbo build --filter='!@xtablo/external'
|
||||
```
|
||||
|
||||
### Package-Level Configuration
|
||||
|
||||
Packages can have their own `turbo.json` file to define custom tasks or override root configuration:
|
||||
|
||||
**Example:** `apps/main/turbo.json` defines environment-specific builds:
|
||||
|
||||
- `build:staging` - Builds for staging with `.env.staging`
|
||||
- `build:prod` - Builds for production with `.env.production`
|
||||
|
||||
Each task:
|
||||
|
||||
- Extends the root config with `"extends": ["//"]`
|
||||
- Defines specific inputs (including environment files)
|
||||
- Configures caching with appropriate outputs
|
||||
- Can pass environment variables to the build process
|
||||
|
||||
## Package Development Workflow
|
||||
|
||||
### 1. Working on Packages
|
||||
|
|
@ -244,6 +266,8 @@ pnpm build
|
|||
|
||||
## CI/CD Considerations
|
||||
|
||||
### Basic Pipeline
|
||||
|
||||
For CI/CD pipelines:
|
||||
|
||||
```bash
|
||||
|
|
@ -259,6 +283,45 @@ pnpm typecheck
|
|||
pnpm test
|
||||
```
|
||||
|
||||
### Environment-Specific Deployments
|
||||
|
||||
**Staging Pipeline:**
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
# Build for staging
|
||||
pnpm build:staging
|
||||
|
||||
# Deploy (example with wrangler)
|
||||
cd apps/main && pnpm deploy:staging
|
||||
```
|
||||
|
||||
**Production Pipeline:**
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
# Run all checks
|
||||
pnpm lint
|
||||
pnpm typecheck
|
||||
pnpm test
|
||||
|
||||
# Build for production
|
||||
pnpm build:prod
|
||||
|
||||
# Deploy (example with wrangler)
|
||||
cd apps/main && pnpm deploy:prod
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
|
||||
- Turborepo caches builds per environment
|
||||
- Environment-specific `.env` files are tracked as inputs
|
||||
- Builds are only re-run when relevant files change
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Turborepo Documentation](https://turbo.build/repo/docs)
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ pnpm dev:external # Run external app only
|
|||
# Building
|
||||
pnpm build # Build all apps
|
||||
pnpm build:apps # Build apps only
|
||||
pnpm build:staging # Build main app for staging
|
||||
pnpm build:prod # Build main app for production
|
||||
|
||||
# Testing & Quality
|
||||
pnpm test # Run all tests
|
||||
|
|
|
|||
51
apps/external/biome.json
vendored
51
apps/external/biome.json
vendored
|
|
@ -1,22 +1,9 @@
|
|||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.2.5/schema.json",
|
||||
"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
|
||||
"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": false },
|
||||
"files": {
|
||||
"includes": [
|
||||
"ui/src/**/*",
|
||||
"ui/worker/**/*",
|
||||
"ui/*.{ts,tsx,js,jsx,json}",
|
||||
"api/src/**/*",
|
||||
"api/*.{ts,tsx,js,jsx,json}",
|
||||
"xtablo-expo/app/**/*",
|
||||
"xtablo-expo/components/**/*",
|
||||
"xtablo-expo/hooks/**/*",
|
||||
"xtablo-expo/lib/**/*",
|
||||
"xtablo-expo/providers/**/*",
|
||||
"xtablo-expo/stores/**/*",
|
||||
"xtablo-expo/types/**/*",
|
||||
"xtablo-expo/*.{ts,tsx,js,jsx,json}"
|
||||
]
|
||||
"ignoreUnknown": true,
|
||||
"includes": ["src/**/*", "*.{ts,tsx,js,jsx,json}"]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
|
|
@ -278,14 +265,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"includes": [
|
||||
"ui/src/**/*.{ts,tsx}",
|
||||
"ui/worker/**/*.{ts,tsx}",
|
||||
"ui/*.{ts,tsx}",
|
||||
"api/src/**/*.{ts,tsx}",
|
||||
"api/*.{ts,tsx}",
|
||||
"xtablo-expo/**/*.{ts,tsx}"
|
||||
],
|
||||
"includes": ["src/**/*.{ts,tsx}", "*.{ts,tsx}"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"complexity": { "noArguments": "error" },
|
||||
|
|
@ -314,29 +294,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"includes": ["xtablo-expo/**/*.{js,jsx,ts,tsx}"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"correctness": {
|
||||
"noUndeclaredVariables": "off"
|
||||
},
|
||||
"suspicious": {
|
||||
"noExplicitAny": "warn"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"includes": ["api/src/**/*.{js,ts}"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"style": {
|
||||
"noCommonJs": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
1
apps/external/package.json
vendored
1
apps/external/package.json
vendored
|
|
@ -39,4 +39,3 @@
|
|||
"zustand": "^5.0.5"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
2
apps/external/src/CustomModal.tsx
vendored
2
apps/external/src/CustomModal.tsx
vendored
|
|
@ -1,5 +1,5 @@
|
|||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@xtablo/ui/components/dialog";
|
||||
import { cn } from "@xtablo/shared";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@xtablo/ui/components/dialog";
|
||||
|
||||
// Custom Modal Component - now using shadcn/ui Dialog
|
||||
interface CustomModalProps {
|
||||
|
|
|
|||
36
apps/external/src/EmbeddedBookingPage.tsx
vendored
36
apps/external/src/EmbeddedBookingPage.tsx
vendored
|
|
@ -1,16 +1,18 @@
|
|||
import { CustomModal } from "./CustomModal";
|
||||
import { LoadingSpinner } from "./LoadingSpinner";
|
||||
import { useCreateTabloWithOwner } from "@xtablo/shared";
|
||||
import { useSession } from "@xtablo/shared/contexts/SessionContext";
|
||||
import { useSignUpWithoutPassword } from "@xtablo/shared/hooks/auth";
|
||||
import { TimeSlot, usePublicSlots } from "@xtablo/shared/hooks/public";
|
||||
import { EventInsertInTablo } from "@xtablo/shared/types/events.types";
|
||||
import { Button } from "@xtablo/ui/components/button";
|
||||
import { FieldError } from "@xtablo/ui/components/field";
|
||||
import { Input } from "@xtablo/ui/components/input";
|
||||
import { Label } from "@xtablo/ui/components/label";
|
||||
import { Text, TypographyH3, TypographyH4, TypographyMuted } from "@xtablo/ui/components/typography";
|
||||
import { useSession } from "@xtablo/shared/contexts/SessionContext";
|
||||
import { useSignUpWithoutPassword } from "@xtablo/shared/hooks/auth";
|
||||
import { TimeSlot, usePublicSlots } from "@xtablo/shared/hooks/public";
|
||||
import { useCreateTabloWithOwner } from "@xtablo/shared";
|
||||
import { useMaybeUser } from "./UserStoreProvider";
|
||||
import { EventInsertInTablo } from "@xtablo/shared/types/events.types";
|
||||
import {
|
||||
Text,
|
||||
TypographyH3,
|
||||
TypographyH4,
|
||||
TypographyMuted,
|
||||
} from "@xtablo/ui/components/typography";
|
||||
import {
|
||||
CalendarIcon,
|
||||
ChevronLeftIcon,
|
||||
|
|
@ -22,8 +24,11 @@ import {
|
|||
import { useState } from "react";
|
||||
import { useParams, useSearchParams } from "react-router-dom";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { supabase } from "./lib/supabase";
|
||||
import { CustomModal } from "./CustomModal";
|
||||
import { LoadingSpinner } from "./LoadingSpinner";
|
||||
import { api } from "./lib/api";
|
||||
import { supabase } from "./lib/supabase";
|
||||
import { useMaybeUser } from "./UserStoreProvider";
|
||||
|
||||
type ColorVariant = "black" | "white" | "blue" | "purple" | "green" | "orange" | "red";
|
||||
|
||||
|
|
@ -209,19 +214,16 @@ export function EmbeddedBookingPage() {
|
|||
|
||||
const shortUserId = userInfo?.substring(userInfo.lastIndexOf("-") + 1);
|
||||
|
||||
console.log({shortUserId, eventTypeStandardName})
|
||||
console.log({ shortUserId, eventTypeStandardName });
|
||||
const { data: publicSlots, isLoading: isLoadingSlots } = usePublicSlots(
|
||||
api,
|
||||
shortUserId || "",
|
||||
eventTypeStandardName || ""
|
||||
);
|
||||
|
||||
|
||||
const { mutateAsync: createTabloWithOwner } = useCreateTabloWithOwner(api,
|
||||
() => {
|
||||
handleCloseModal();
|
||||
}
|
||||
);
|
||||
const { mutateAsync: createTabloWithOwner } = useCreateTabloWithOwner(api, () => {
|
||||
handleCloseModal();
|
||||
});
|
||||
|
||||
const userProfile = publicSlots?.user;
|
||||
const eventType = publicSlots?.eventType;
|
||||
|
|
|
|||
30
apps/external/src/FloatingBookingWidget.tsx
vendored
30
apps/external/src/FloatingBookingWidget.tsx
vendored
|
|
@ -1,16 +1,13 @@
|
|||
import { CustomModal } from "./CustomModal";
|
||||
import { LoadingSpinner } from "./LoadingSpinner";
|
||||
import { useCreateTabloWithOwner } from "@xtablo/shared";
|
||||
import { useSession } from "@xtablo/shared/contexts/SessionContext";
|
||||
import { useSignUpWithoutPassword } from "@xtablo/shared/hooks/auth";
|
||||
import { TimeSlot, usePublicSlots } from "@xtablo/shared/hooks/public";
|
||||
import { EventInsertInTablo } from "@xtablo/shared/types/events.types";
|
||||
import { Button } from "@xtablo/ui/components/button";
|
||||
import { FieldError } from "@xtablo/ui/components/field";
|
||||
import { Input } from "@xtablo/ui/components/input";
|
||||
import { Label } from "@xtablo/ui/components/label";
|
||||
import { Text, TypographyH4, TypographyMuted } from "@xtablo/ui/components/typography";
|
||||
import { useSession } from "@xtablo/shared/contexts/SessionContext";
|
||||
import { useSignUpWithoutPassword } from "@xtablo/shared/hooks/auth";
|
||||
import { TimeSlot, usePublicSlots } from "@xtablo/shared/hooks/public";
|
||||
// import { useCreateTabloWithOwner } from "@xtablo/shared";
|
||||
import { useMaybeUser } from "./UserStoreProvider";
|
||||
import { EventInsertInTablo } from "@xtablo/shared/types/events.types";
|
||||
import {
|
||||
CalendarIcon,
|
||||
ChevronLeftIcon,
|
||||
|
|
@ -23,9 +20,12 @@ import {
|
|||
import { useState } from "react";
|
||||
import { useParams, useSearchParams } from "react-router-dom";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { supabase } from "./lib/supabase";
|
||||
import { CustomModal } from "./CustomModal";
|
||||
import { LoadingSpinner } from "./LoadingSpinner";
|
||||
import { api } from "./lib/api";
|
||||
import { useCreateTabloWithOwner } from "@xtablo/shared";
|
||||
import { supabase } from "./lib/supabase";
|
||||
// import { useCreateTabloWithOwner } from "@xtablo/shared";
|
||||
import { useMaybeUser } from "./UserStoreProvider";
|
||||
|
||||
type ColorVariant = "black" | "white" | "blue" | "purple" | "green" | "orange" | "red";
|
||||
|
||||
|
|
@ -132,12 +132,10 @@ export function FloatingBookingWidget() {
|
|||
eventTypeStandardName || ""
|
||||
);
|
||||
|
||||
const { mutateAsync: createTabloWithOwner } = useCreateTabloWithOwner(api,
|
||||
() => {
|
||||
handleCloseModal();
|
||||
setIsWidgetOpen(false);
|
||||
}
|
||||
);
|
||||
const { mutateAsync: createTabloWithOwner } = useCreateTabloWithOwner(api, () => {
|
||||
handleCloseModal();
|
||||
setIsWidgetOpen(false);
|
||||
});
|
||||
|
||||
const userProfile = publicSlots?.user;
|
||||
const eventType = publicSlots?.eventType;
|
||||
|
|
|
|||
3
apps/external/src/UserStoreProvider.tsx
vendored
3
apps/external/src/UserStoreProvider.tsx
vendored
|
|
@ -1,9 +1,9 @@
|
|||
import { useQuery } from "@tanstack/react-query";
|
||||
import { LoadingSpinner } from "./LoadingSpinner";
|
||||
import { useSession } from "@xtablo/shared/contexts/SessionContext";
|
||||
import { Tables } from "@xtablo/shared/types/database.types";
|
||||
import React from "react";
|
||||
import { createStore, StoreApi, useStore } from "zustand";
|
||||
import { LoadingSpinner } from "./LoadingSpinner";
|
||||
import { api } from "./lib/api";
|
||||
|
||||
export type User = Tables<"profiles"> & {
|
||||
|
|
@ -12,7 +12,6 @@ export type User = Tables<"profiles"> & {
|
|||
|
||||
const UserStoreContext = React.createContext<StoreApi<User> | null>(null);
|
||||
|
||||
|
||||
export const UserStoreProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const { session } = useSession();
|
||||
const shouldFetchUser = !!session?.access_token;
|
||||
|
|
|
|||
15
apps/external/src/main.css
vendored
15
apps/external/src/main.css
vendored
|
|
@ -301,8 +301,7 @@
|
|||
transform: translate(-50%, -50%) rotate(0deg) translateX(150px) rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate(-50%, -50%) rotate(360deg) translateX(150px)
|
||||
rotate(-360deg);
|
||||
transform: translate(-50%, -50%) rotate(360deg) translateX(150px) rotate(-360deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -311,8 +310,7 @@
|
|||
transform: translate(-50%, -50%) rotate(0deg) translateX(200px) rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate(-50%, -50%) rotate(-360deg) translateX(200px)
|
||||
rotate(360deg);
|
||||
transform: translate(-50%, -50%) rotate(-360deg) translateX(200px) rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -321,8 +319,7 @@
|
|||
transform: translate(-50%, -50%) rotate(0deg) translateX(100px) rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate(-50%, -50%) rotate(360deg) translateX(100px)
|
||||
rotate(-360deg);
|
||||
transform: translate(-50%, -50%) rotate(360deg) translateX(100px) rotate(-360deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -500,8 +497,7 @@
|
|||
transform: translate(-50%, -50%) rotate(0deg) translateX(250px) rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate(-50%, -50%) rotate(360deg) translateX(250px)
|
||||
rotate(-360deg);
|
||||
transform: translate(-50%, -50%) rotate(360deg) translateX(250px) rotate(-360deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -510,8 +506,7 @@
|
|||
transform: translate(-50%, -50%) rotate(0deg) translateX(120px) rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate(-50%, -50%) rotate(-360deg) translateX(120px)
|
||||
rotate(360deg);
|
||||
transform: translate(-50%, -50%) rotate(-360deg) translateX(120px) rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
5
apps/external/src/main.tsx
vendored
5
apps/external/src/main.tsx
vendored
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
import { QueryClientProvider } from "@tanstack/react-query";
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { queryClient } from "@xtablo/shared";
|
||||
import { ThemeProvider } from "@xtablo/shared/contexts/ThemeContext";
|
||||
import { Toaster } from "@xtablo/ui/components/sonner";
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
import AppRoutes from "./routes";
|
||||
|
||||
|
|
|
|||
6
apps/external/src/routes.tsx
vendored
6
apps/external/src/routes.tsx
vendored
|
|
@ -1,6 +1,6 @@
|
|||
import { Routes, Route } from 'react-router-dom';
|
||||
import { EmbeddedBookingPage } from './EmbeddedBookingPage';
|
||||
import { FloatingBookingWidget } from './FloatingBookingWidget';
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import { EmbeddedBookingPage } from "./EmbeddedBookingPage";
|
||||
import { FloatingBookingWidget } from "./FloatingBookingWidget";
|
||||
|
||||
export default function AppRoutes() {
|
||||
return (
|
||||
|
|
|
|||
1
apps/external/tsconfig.json
vendored
1
apps/external/tsconfig.json
vendored
|
|
@ -26,4 +26,3 @@
|
|||
"include": ["src"],
|
||||
"references": []
|
||||
}
|
||||
|
||||
|
|
|
|||
4
apps/main/.biomeignore
Normal file
4
apps/main/.biomeignore
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# Generated Wrangler files
|
||||
worker-configuration.d.ts
|
||||
worker/
|
||||
|
||||
|
|
@ -1,22 +1,10 @@
|
|||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.2.5/schema.json",
|
||||
"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
|
||||
"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": false },
|
||||
"files": {
|
||||
"includes": [
|
||||
"ui/src/**/*",
|
||||
"ui/worker/**/*",
|
||||
"ui/*.{ts,tsx,js,jsx,json}",
|
||||
"api/src/**/*",
|
||||
"api/*.{ts,tsx,js,jsx,json}",
|
||||
"xtablo-expo/app/**/*",
|
||||
"xtablo-expo/components/**/*",
|
||||
"xtablo-expo/hooks/**/*",
|
||||
"xtablo-expo/lib/**/*",
|
||||
"xtablo-expo/providers/**/*",
|
||||
"xtablo-expo/stores/**/*",
|
||||
"xtablo-expo/types/**/*",
|
||||
"xtablo-expo/*.{ts,tsx,js,jsx,json}"
|
||||
]
|
||||
"ignoreUnknown": true,
|
||||
"maxSize": 10485760,
|
||||
"includes": ["src/**/*"]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
|
|
@ -278,14 +266,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"includes": [
|
||||
"ui/src/**/*.{ts,tsx}",
|
||||
"ui/worker/**/*.{ts,tsx}",
|
||||
"ui/*.{ts,tsx}",
|
||||
"api/src/**/*.{ts,tsx}",
|
||||
"api/*.{ts,tsx}",
|
||||
"xtablo-expo/**/*.{ts,tsx}"
|
||||
],
|
||||
"includes": ["src/**/*.{ts,tsx}", "worker/**/*.{ts,tsx}", "*.{ts,tsx}"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"complexity": { "noArguments": "error" },
|
||||
|
|
@ -314,29 +295,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"includes": ["xtablo-expo/**/*.{js,jsx,ts,tsx}"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"correctness": {
|
||||
"noUndeclaredVariables": "off"
|
||||
},
|
||||
"suspicious": {
|
||||
"noExplicitAny": "warn"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"includes": ["api/src/**/*.{js,ts}"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"style": {
|
||||
"noCommonJs": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,7 @@
|
|||
"preview": "vite preview",
|
||||
"build:staging": "tsc -b && vite build --mode staging",
|
||||
"build:prod": "tsc -b && vite build --mode production",
|
||||
"deploy:staging": "tsc -b && vite build --mode staging && wrangler deploy --env=\"\"",
|
||||
"deploy:prod": "tsc -b && vite build --mode production && wrangler deploy --env=\"\"",
|
||||
"deploy": "wrangler deploy --env=\"\"",
|
||||
"cf-typegen": "wrangler types",
|
||||
"test": "vitest run --mode dev --passWithNoTests",
|
||||
"test:watch": "vitest watch --passWithNoTests",
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { Toaster } from "@xtablo/ui/components/sonner";
|
||||
import { SessionProvider } from "@xtablo/shared/contexts/SessionContext";
|
||||
import { ThemeProvider } from "@xtablo/shared/contexts/ThemeContext";
|
||||
import { UserStoreProvider } from "./providers/UserStoreProvider";
|
||||
import { routes } from "./lib/routes";
|
||||
import { DatadogRumProvider } from "./providers/DatadogRumProvider";
|
||||
import { Toaster } from "@xtablo/ui/components/sonner";
|
||||
import { BrowserRouter as Router, useRoutes } from "react-router-dom";
|
||||
import { routes } from "./lib/routes";
|
||||
import { supabase } from "./lib/supabase";
|
||||
import { DatadogRumProvider } from "./providers/DatadogRumProvider";
|
||||
import { UserStoreProvider } from "./providers/UserStoreProvider";
|
||||
|
||||
const AppRoutes = () => {
|
||||
const element = useRoutes(routes);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { Navigate, Outlet, useSearchParams } from "react-router-dom";
|
||||
import { match } from "ts-pattern";
|
||||
import { LoadingSpinner } from "./LoadingSpinner";
|
||||
import { useMaybeUser } from "../providers/UserStoreProvider";
|
||||
import { LoadingSpinner } from "./LoadingSpinner";
|
||||
|
||||
export const AuthenticationGateway = () => {
|
||||
const user = useMaybeUser();
|
||||
|
|
|
|||
|
|
@ -1,26 +1,28 @@
|
|||
import { screen, waitFor } from "@testing-library/react";
|
||||
import { AuthenticationGateway } from "@ui/components/AuthenticationGateway";
|
||||
import { SessionTestProvider } from "@xtablo/shared/contexts/SessionContext";
|
||||
import { renderWithRouter } from "../utils/testHelpers";
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import { renderWithRouter } from "../utils/testHelpers";
|
||||
|
||||
describe("PublicRoute", () => {
|
||||
it("shows loading state initially", () => {
|
||||
renderWithRouter(
|
||||
<SessionTestProvider testUser={{
|
||||
id: "123",
|
||||
app_metadata: {},
|
||||
user_metadata: {
|
||||
full_name: "Test User",
|
||||
email: "test@example.com",
|
||||
email_verified: true,
|
||||
first_name: "Test",
|
||||
last_name: "User",
|
||||
business_name: "Test Business",
|
||||
},
|
||||
aud: "authenticated",
|
||||
created_at: new Date().toISOString(),
|
||||
}}>
|
||||
<SessionTestProvider
|
||||
testUser={{
|
||||
id: "123",
|
||||
app_metadata: {},
|
||||
user_metadata: {
|
||||
full_name: "Test User",
|
||||
email: "test@example.com",
|
||||
email_verified: true,
|
||||
first_name: "Test",
|
||||
last_name: "User",
|
||||
business_name: "Test Business",
|
||||
},
|
||||
aud: "authenticated",
|
||||
created_at: new Date().toISOString(),
|
||||
}}
|
||||
>
|
||||
<Routes>
|
||||
<Route element={<AuthenticationGateway />}>
|
||||
<Route path="/login" element={<div>Login Page</div>} />
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import { LoginWithGoogle } from "@ui/components/BrandButtons/LoginWithGoogle";
|
||||
import { useLoginGoogle } from "../../hooks/auth";
|
||||
import { vi } from "vitest";
|
||||
import { useLoginGoogle } from "../../hooks/auth";
|
||||
|
||||
vi.mock("../../hooks/auth", () => ({
|
||||
useLoginGoogle: vi.fn(),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ChannelBadge } from "@ui/components/ChannelBadge";
|
||||
import { Badge } from "@xtablo/ui/components/badge";
|
||||
import { UserTablo } from "@xtablo/shared/types/tablos.types";
|
||||
import { Badge } from "@xtablo/ui/components/badge";
|
||||
import { ReactNode } from "react";
|
||||
import { Channel } from "stream-chat";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import { useClickOutside } from "@xtablo/shared/hooks/useClickOutside";
|
||||
import React from "react";
|
||||
|
||||
interface ClickOutsideProps {
|
||||
children: React.ReactNode;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@xtablo/ui/components/dialog";
|
||||
import { cn } from "@xtablo/shared";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@xtablo/ui/components/dialog";
|
||||
|
||||
// Custom Modal Component - now using shadcn/ui Dialog
|
||||
interface CustomModalProps {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Button } from "@xtablo/ui/components/button";
|
||||
import { CopyButton } from "@xtablo/ui/components/clipboard";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
|
|
@ -14,9 +15,8 @@ import {
|
|||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@xtablo/ui/components/select";
|
||||
import { CopyButton } from "@xtablo/ui/components/clipboard";
|
||||
import { useState } from "react";
|
||||
import { TypographyMuted, TypographyP } from "@xtablo/ui/components/typography";
|
||||
import { useState } from "react";
|
||||
|
||||
type ColorVariant = "black" | "white" | "blue" | "purple" | "green" | "orange" | "red";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { EventAndTablo } from "@xtablo/shared/types/events.types";
|
||||
import { Button } from "@xtablo/ui/components/button";
|
||||
import { Strong, Text } from "@xtablo/ui/components/typography";
|
||||
import { EventAndTablo } from "@xtablo/shared/types/events.types";
|
||||
import { CalendarIcon, User } from "lucide-react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { CustomModal } from "./CustomModal";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { getLocalTimeZone, parseDate, today } from "@internationalized/date";
|
||||
import { Event, EventInsert } from "@xtablo/shared/types/events.types";
|
||||
import { Button } from "@xtablo/ui/components/button";
|
||||
import { DatePicker } from "@xtablo/ui/components/date-picker";
|
||||
import {
|
||||
|
|
@ -20,12 +21,11 @@ import {
|
|||
} from "@xtablo/ui/components/select";
|
||||
import { Textarea } from "@xtablo/ui/components/textarea";
|
||||
import { TimeInput } from "@xtablo/ui/components/time-input";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||
import { useCreateEvents, useEvent, useUpdateEvent } from "../hooks/events";
|
||||
import { useTablosList } from "../hooks/tablos";
|
||||
import { useUser } from "../providers/UserStoreProvider";
|
||||
import { Event, EventInsert } from "@xtablo/shared/types/events.types";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||
|
||||
export const EventModal = ({ mode }: { mode: "create" | "edit" }) => {
|
||||
const { event_id } = useParams();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { EmbedConfigModal, EmbedType } from "@ui/components/EmbedConfigModal";
|
||||
import { Button } from "@xtablo/ui/components/button";
|
||||
import {
|
||||
Card,
|
||||
|
|
@ -7,8 +8,6 @@ import {
|
|||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@xtablo/ui/components/card";
|
||||
import { EmbedConfigModal, EmbedType } from "@ui/components/EmbedConfigModal";
|
||||
import { EventType, EventTypeConfig, useEventTypes } from "../hooks/event-types";
|
||||
import {
|
||||
CheckIcon,
|
||||
EditIcon,
|
||||
|
|
@ -18,8 +17,9 @@ import {
|
|||
XIcon,
|
||||
} from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useUser } from "../providers/UserStoreProvider";
|
||||
import { match } from "ts-pattern";
|
||||
import { EventType, EventTypeConfig, useEventTypes } from "../hooks/event-types";
|
||||
import { useUser } from "../providers/UserStoreProvider";
|
||||
|
||||
export function EventTypeCard({
|
||||
eventType,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { toast } from "@xtablo/shared";
|
||||
import { Button } from "@xtablo/ui/components/button";
|
||||
import { ButtonGroup } from "@xtablo/ui/components/button-group";
|
||||
import { DatePickerV1 } from "@xtablo/ui/components/date-picker";
|
||||
|
|
@ -12,11 +13,10 @@ import {
|
|||
} from "@xtablo/ui/components/dialog";
|
||||
import { Label } from "@xtablo/ui/components/label";
|
||||
import { TimeInput } from "@xtablo/ui/components/time-input";
|
||||
import { Exception } from "../hooks/availabilities";
|
||||
import { toast } from "@xtablo/shared";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import * as z from "zod";
|
||||
import { Exception } from "../hooks/availabilities";
|
||||
|
||||
const formSchema = z.object({
|
||||
exceptionType: z.enum(["day", "hours"]),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import { useState, useCallback } from "react";
|
||||
import Cropper from "react-easy-crop";
|
||||
import type { Area } from "react-easy-crop";
|
||||
import { Button } from "@xtablo/ui/components/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
|
|
@ -9,9 +7,11 @@ import {
|
|||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@xtablo/ui/components/dialog";
|
||||
import { Button } from "@xtablo/ui/components/button";
|
||||
import { Slider } from "@xtablo/ui/components/slider";
|
||||
import { Label } from "@xtablo/ui/components/label";
|
||||
import { Slider } from "@xtablo/ui/components/slider";
|
||||
import { useCallback, useState } from "react";
|
||||
import type { Area } from "react-easy-crop";
|
||||
import Cropper from "react-easy-crop";
|
||||
|
||||
interface ImageCropDialogProps {
|
||||
open: boolean;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import { ParsedICSEvent, parseICSFile, toast } from "@xtablo/shared";
|
||||
import { EventInsert } from "@xtablo/shared/types/events.types";
|
||||
import { CreateTablo } from "@xtablo/shared/types/tablos.types";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
|
|
@ -5,14 +8,10 @@ import {
|
|||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@xtablo/ui/components/select";
|
||||
import { useRef, useState } from "react";
|
||||
import { useCreateEvents } from "../hooks/events";
|
||||
import { useCreateTablo, useTablosList } from "../hooks/tablos";
|
||||
import { toast } from "@xtablo/shared";
|
||||
import { useUser } from "../providers/UserStoreProvider";
|
||||
import { EventInsert } from "@xtablo/shared/types/events.types";
|
||||
import { CreateTablo } from "@xtablo/shared/types/tablos.types";
|
||||
import { ParsedICSEvent, parseICSFile } from "@xtablo/shared";
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
interface ImportICSModalProps {
|
||||
onClose: () => void;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import { Layout } from "@ui/components/Layout";
|
||||
import { SessionTestProvider } from "@xtablo/shared/contexts/SessionContext";
|
||||
import { renderWithProviders } from "../utils/testHelpers";
|
||||
import { SessionTestProvider } from "@xtablo/shared/contexts/SessionContext";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import { renderWithProviders } from "../utils/testHelpers";
|
||||
|
||||
describe("Layout", () => {
|
||||
it("renders the layout with children", () => {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
// shadcn components
|
||||
import {
|
||||
Avatar,
|
||||
AvatarBadge,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@xtablo/ui/components/avatar";
|
||||
|
||||
import { cn } from "@xtablo/shared/lib/cn.ts";
|
||||
import { Avatar, AvatarBadge, AvatarFallback, AvatarImage } from "@xtablo/ui/components/avatar";
|
||||
import { Button } from "@xtablo/ui/components/button";
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@xtablo/ui/components/dropdown-menu";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@xtablo/ui/components/dropdown-menu";
|
||||
import { TypographyLarge, TypographyMuted } from "@xtablo/ui/components/typography";
|
||||
import { useUser } from "../providers/UserStoreProvider";
|
||||
import { isProd, isStaging } from "../lib/env";
|
||||
import { getXtabloIcon } from "../utils/iconHelpers";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import {
|
||||
CalendarCheckIcon,
|
||||
CalendarIcon,
|
||||
|
|
@ -31,10 +32,11 @@ import { useState } from "react";
|
|||
import { Separator } from "react-aria-components";
|
||||
import { Link as RouterLink, useLocation } from "react-router-dom";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { ThemeSwitcher } from "./ThemeSwitcher";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { cn } from "@xtablo/shared/lib/cn.ts";
|
||||
import { useLogout } from "../hooks/auth";
|
||||
import { isProd, isStaging } from "../lib/env";
|
||||
import { useUser } from "../providers/UserStoreProvider";
|
||||
import { getXtabloIcon } from "../utils/iconHelpers";
|
||||
import { ThemeSwitcher } from "./ThemeSwitcher";
|
||||
|
||||
type NavLinkItem = {
|
||||
isActive?: boolean;
|
||||
|
|
@ -161,7 +163,7 @@ export function UserMenuPopover({ isCollapsed }: { isCollapsed: boolean }) {
|
|||
</div>
|
||||
|
||||
<MenuSeparator />
|
||||
|
||||
|
||||
<MenuDropdownItem
|
||||
icon={<LogOutIcon className="w-5 h-5" aria-hidden="true" />}
|
||||
label="Se déconnecter"
|
||||
|
|
@ -414,4 +416,4 @@ export function MainNavigation({ isCollapsed }: { isCollapsed: boolean }) {
|
|||
</ul>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { screen, waitFor } from "@testing-library/react";
|
||||
import { ProtectedRoute } from "@ui/components/ProtectedRoute";
|
||||
import { SessionTestProvider } from "@xtablo/shared/contexts/SessionContext";
|
||||
import { renderWithRouter } from "../utils/testHelpers";
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import { TestUserStoreProvider } from "../providers/UserStoreProvider";
|
||||
import { renderWithRouter } from "../utils/testHelpers";
|
||||
|
||||
describe("ProtectedRoute", () => {
|
||||
beforeEach(() => {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { Navigate, Outlet } from "react-router-dom";
|
||||
import { match } from "ts-pattern";
|
||||
import { LoadingSpinner } from "./LoadingSpinner";
|
||||
import { useMaybeUser } from "../providers/UserStoreProvider";
|
||||
import { LoadingSpinner } from "./LoadingSpinner";
|
||||
|
||||
interface ProtectedRouteProps {
|
||||
fallback?: string;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
import { toast } from "@xtablo/shared";
|
||||
import { TabloUpdate, UserTablo } from "@xtablo/shared/types/tablos.types";
|
||||
import { Button } from "@xtablo/ui/components/button";
|
||||
import { DownloadIcon, Trash2Icon } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { FileTrigger } from "react-aria-components";
|
||||
import { useInviteUser } from "../hooks/invite";
|
||||
import {
|
||||
useCreateTabloFile,
|
||||
|
|
@ -7,12 +12,7 @@ import {
|
|||
useTabloFileNames,
|
||||
} from "../hooks/tablo_data";
|
||||
import { useTabloMembers } from "../hooks/tablos";
|
||||
import { toast } from "@xtablo/shared";
|
||||
import { useUser } from "../providers/UserStoreProvider";
|
||||
import { TabloUpdate, UserTablo } from "@xtablo/shared/types/tablos.types";
|
||||
import { FileTrigger } from "react-aria-components";
|
||||
import { DownloadIcon, Trash2Icon } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { ClickOutside } from "./ClickOutside";
|
||||
import { ImageColorPicker } from "./ImageColorPicker";
|
||||
import { StatusPicker } from "./StatusPicker";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useTheme } from "@xtablo/shared/contexts/ThemeContext";
|
||||
import { Button } from "@xtablo/ui/components/button";
|
||||
import { ButtonGroup } from "@xtablo/ui/components/button-group";
|
||||
import { useTheme } from "@xtablo/shared/contexts/ThemeContext";
|
||||
import { MonitorIcon, MoonIcon, SunIcon } from "lucide-react";
|
||||
|
||||
const translation = {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { toast } from "@xtablo/shared";
|
||||
import { Button } from "@xtablo/ui/components/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
|
|
@ -5,16 +7,20 @@ import {
|
|||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@xtablo/ui/components/dialog";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@xtablo/ui/components/select";
|
||||
import { Button } from "@xtablo/ui/components/button";
|
||||
import { Input } from "@xtablo/ui/components/input";
|
||||
import { Label } from "@xtablo/ui/components/label";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@xtablo/ui/components/select";
|
||||
import { CopyIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useTablosList } from "../hooks/tablos";
|
||||
import { useGenerateWebcalToken } from "../hooks/webcal";
|
||||
import { toast } from "@xtablo/shared";
|
||||
import { useState } from "react";
|
||||
|
||||
import { CopyIcon } from "lucide-react";
|
||||
interface WebcalModalProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
|
|
@ -37,7 +43,7 @@ export const WebcalModal = ({ open, onOpenChange }: WebcalModalProps) => {
|
|||
},
|
||||
{ timeout: 2000 }
|
||||
);
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
toast.add(
|
||||
{
|
||||
title: "Erreur",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Button } from "@xtablo/ui/components/button";
|
||||
import { getXtabloIcon } from "../utils/iconHelpers";
|
||||
import { Link } from "react-router-dom";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { getXtabloIcon } from "../utils/iconHelpers";
|
||||
|
||||
export function Header() {
|
||||
const logo = getXtabloIcon();
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
import { Session, User as SupabaseUser } from "@supabase/supabase-js";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { api } from "../lib/api";
|
||||
import { queryClient } from "@xtablo/shared";
|
||||
import { toast } from "@xtablo/shared";
|
||||
import { queryClient, toast, useSession } from "@xtablo/shared";
|
||||
import { useSignUpToStream } from "@xtablo/shared/hooks/auth";
|
||||
import { AxiosInstance } from "axios";
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { match } from "ts-pattern";
|
||||
import { api } from "../lib/api";
|
||||
import { supabase } from "../lib/supabase";
|
||||
import { AxiosInstance } from "axios";
|
||||
import { useSession } from "@xtablo/shared";
|
||||
import { useSignUpToStream } from "@xtablo/shared/hooks/auth";
|
||||
|
||||
export type User = SupabaseUser & {
|
||||
user_metadata: {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { supabase } from "../lib/supabase";
|
||||
import { queryClient } from "@xtablo/shared";
|
||||
import { queryClient, toast } from "@xtablo/shared";
|
||||
import { Database } from "@xtablo/shared/types/database.types";
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "@xtablo/shared";
|
||||
import { supabase } from "../lib/supabase";
|
||||
import { useUser } from "../providers/UserStoreProvider";
|
||||
|
||||
export type TimeRange = {
|
||||
|
|
@ -34,23 +33,20 @@ export type Exception = {
|
|||
|
||||
const DAYS_OF_WEEK = [0, 1, 2, 3, 4, 5, 6];
|
||||
|
||||
export const DEFAULT_AVAILABILITIES: WeeklyAvailability = DAYS_OF_WEEK.reduce(
|
||||
(acc, day) => {
|
||||
if (day === 5 || day === 6) {
|
||||
acc[day] = {
|
||||
enabled: false,
|
||||
timeRanges: [{ start: "09:00", end: "17:00" }],
|
||||
};
|
||||
} else {
|
||||
acc[day] = {
|
||||
enabled: true,
|
||||
timeRanges: [{ start: "09:00", end: "17:00" }],
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{} as WeeklyAvailability
|
||||
);
|
||||
export const DEFAULT_AVAILABILITIES: WeeklyAvailability = DAYS_OF_WEEK.reduce((acc, day) => {
|
||||
if (day === 5 || day === 6) {
|
||||
acc[day] = {
|
||||
enabled: false,
|
||||
timeRanges: [{ start: "09:00", end: "17:00" }],
|
||||
};
|
||||
} else {
|
||||
acc[day] = {
|
||||
enabled: true,
|
||||
timeRanges: [{ start: "09:00", end: "17:00" }],
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
}, {} as WeeklyAvailability);
|
||||
|
||||
export function useAvailabilities() {
|
||||
const user = useUser();
|
||||
|
|
@ -86,8 +82,7 @@ export function useAvailabilities() {
|
|||
newException?: Exception | null;
|
||||
}) => {
|
||||
const newAvailabilities = updatedAvailabilities;
|
||||
const newExceptions =
|
||||
(availabilities?.exceptions as Exception[] | null) || [];
|
||||
const newExceptions = (availabilities?.exceptions as Exception[] | null) || [];
|
||||
if (newException) {
|
||||
newExceptions.push(newException);
|
||||
}
|
||||
|
|
@ -120,22 +115,14 @@ export function useAvailabilities() {
|
|||
},
|
||||
});
|
||||
|
||||
const { mutate: deleteException } = useMutation<
|
||||
void,
|
||||
Error,
|
||||
{ exceptionIndex: number }
|
||||
>({
|
||||
const { mutate: deleteException } = useMutation<void, Error, { exceptionIndex: number }>({
|
||||
mutationFn: async ({ exceptionIndex }: { exceptionIndex: number }) => {
|
||||
const currentExceptions =
|
||||
(availabilities?.exceptions as Exception[] | null) || [];
|
||||
const updatedExceptions = currentExceptions.filter(
|
||||
(_, index) => index !== exceptionIndex
|
||||
);
|
||||
const currentExceptions = (availabilities?.exceptions as Exception[] | null) || [];
|
||||
const updatedExceptions = currentExceptions.filter((_, index) => index !== exceptionIndex);
|
||||
|
||||
const { error } = await supabase.from("availabilities").upsert(
|
||||
{
|
||||
availability_data:
|
||||
availabilities?.availability_data || DEFAULT_AVAILABILITIES,
|
||||
availability_data: availabilities?.availability_data || DEFAULT_AVAILABILITIES,
|
||||
exceptions: updatedExceptions,
|
||||
user_id: user.id,
|
||||
},
|
||||
|
|
@ -155,14 +142,11 @@ export function useAvailabilities() {
|
|||
},
|
||||
});
|
||||
|
||||
const [draftAvailabilities, setDraftAvailabilities] =
|
||||
useState<WeeklyAvailability | null>(null);
|
||||
const [draftAvailabilities, setDraftAvailabilities] = useState<WeeklyAvailability | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (availabilities?.availability_data) {
|
||||
setDraftAvailabilities(
|
||||
availabilities.availability_data as WeeklyAvailability
|
||||
);
|
||||
setDraftAvailabilities(availabilities.availability_data as WeeklyAvailability);
|
||||
}
|
||||
}, [availabilities?.availability_data]);
|
||||
|
||||
|
|
|
|||
|
|
@ -25,10 +25,7 @@ export const useDevis = (id: string) => {
|
|||
return useQuery({
|
||||
queryKey: ["devis", id],
|
||||
queryFn: async () => {
|
||||
const { data, error } = await supabase
|
||||
.from("devis")
|
||||
.select("*")
|
||||
.eq("id", id);
|
||||
const { data, error } = await supabase.from("devis").select("*").eq("id", id);
|
||||
if (error) throw error;
|
||||
return data[0];
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import { useMutation, useQuery } from "@tanstack/react-query";
|
|||
import { queryClient } from "@xtablo/shared";
|
||||
import { Database } from "@xtablo/shared/types/database.types";
|
||||
import { useMemo } from "react";
|
||||
import { supabase } from "../lib/supabase";
|
||||
import { useUser } from "src/providers/UserStoreProvider";
|
||||
import { supabase } from "../lib/supabase";
|
||||
|
||||
export type EventTypeData = {
|
||||
id: string;
|
||||
|
|
@ -95,13 +95,7 @@ export function useEventTypes() {
|
|||
eventType: EventTypePayload;
|
||||
}
|
||||
>({
|
||||
mutationFn: async ({
|
||||
id,
|
||||
eventType,
|
||||
}: {
|
||||
id: string;
|
||||
eventType: EventTypePayload;
|
||||
}) => {
|
||||
mutationFn: async ({ id, eventType }: { id: string; eventType: EventTypePayload }) => {
|
||||
const { error } = await supabase
|
||||
.from("event_types")
|
||||
.update({
|
||||
|
|
|
|||
|
|
@ -1,13 +1,8 @@
|
|||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { toast } from "@xtablo/shared";
|
||||
import { useUser } from "../providers/UserStoreProvider";
|
||||
import {
|
||||
Event,
|
||||
EventAndTablo,
|
||||
EventInsert,
|
||||
EventUpdate,
|
||||
} from "@xtablo/shared/types/events.types";
|
||||
import { Event, EventAndTablo, EventInsert, EventUpdate } from "@xtablo/shared/types/events.types";
|
||||
import { supabase } from "../lib/supabase";
|
||||
import { useUser } from "../providers/UserStoreProvider";
|
||||
|
||||
// Fetch events for a specific tablo
|
||||
export const useEventsByTablo = (tabloId: string | null) => {
|
||||
|
|
@ -39,11 +34,7 @@ export const useEventsByTablo = (tabloId: string | null) => {
|
|||
};
|
||||
|
||||
// Fetch events for a specific date range
|
||||
export const useEventsByDateRange = (
|
||||
tabloId: string,
|
||||
startDate: string,
|
||||
endDate: string
|
||||
) => {
|
||||
export const useEventsByDateRange = (tabloId: string, startDate: string, endDate: string) => {
|
||||
return useQuery({
|
||||
queryKey: ["events", tabloId, "dateRange", startDate, endDate],
|
||||
queryFn: async () => {
|
||||
|
|
@ -112,9 +103,7 @@ export const useCreateEvents = () => {
|
|||
queryClient.invalidateQueries({ queryKey: ["events"] });
|
||||
const eventsCount = Array.isArray(data) ? data.length : 1;
|
||||
const pluralizeText =
|
||||
eventsCount === 1
|
||||
? "L'événement a été créé"
|
||||
: "Les événements ont été créés";
|
||||
eventsCount === 1 ? "L'événement a été créé" : "Les événements ont été créés";
|
||||
toast.add(
|
||||
{
|
||||
title: `${eventsCount} événement(s) créé(s)`,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useMutation } from "@tanstack/react-query";
|
||||
import { supabase } from "../lib/supabase";
|
||||
import { FeedbackData } from "../pages/feedback";
|
||||
import { useUser } from "../providers/UserStoreProvider";
|
||||
import { supabase } from "../lib/supabase";
|
||||
|
||||
// Create new feedback
|
||||
export const useCreateFeedback = () => {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import { QueryKey, useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { supabase } from "../lib/supabase";
|
||||
import { toast } from "@xtablo/shared";
|
||||
import { useUser } from "../providers/UserStoreProvider";
|
||||
import { queryClient } from "@xtablo/shared";
|
||||
import { queryClient, toast } from "@xtablo/shared";
|
||||
import { Tables } from "@xtablo/shared/types/database.types";
|
||||
import { useEffect, useState } from "react";
|
||||
import { supabase } from "../lib/supabase";
|
||||
import { useUser } from "../providers/UserStoreProvider";
|
||||
|
||||
type IntroductionConfig = {
|
||||
intro_email: string;
|
||||
|
|
@ -24,8 +23,7 @@ export const useIntroduction = (): {
|
|||
const { mutate: updateIntroduction, isPending: updateIntroductionPending } =
|
||||
useUpsertIntroduction();
|
||||
|
||||
const [draftIntroduction, setDraftIntroduction] =
|
||||
useState<IntroductionConfig | null>(null);
|
||||
const [draftIntroduction, setDraftIntroduction] = useState<IntroductionConfig | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
|
|
@ -112,8 +110,7 @@ export function useUpsertIntroduction() {
|
|||
onError: () => {
|
||||
toast.add({
|
||||
title: "Erreur",
|
||||
description:
|
||||
"Une erreur est survenue lors de la mise à jour de votre introduction",
|
||||
description: "Une erreur est survenue lors de la mise à jour de votre introduction",
|
||||
type: "error",
|
||||
position: "top-center",
|
||||
});
|
||||
|
|
@ -130,10 +127,7 @@ export function useDeleteIntroduction() {
|
|||
|
||||
const { mutate, isPending } = useMutation({
|
||||
mutationFn: async () => {
|
||||
const { error } = await supabase
|
||||
.from("user_introductions")
|
||||
.delete()
|
||||
.eq("user_id", user.id);
|
||||
const { error } = await supabase.from("user_introductions").delete().eq("user_id", user.id);
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
|
|
@ -154,8 +148,7 @@ export function useDeleteIntroduction() {
|
|||
onError: () => {
|
||||
toast.add({
|
||||
title: "Erreur",
|
||||
description:
|
||||
"Une erreur est survenue lors de la suppression de votre introduction",
|
||||
description: "Une erreur est survenue lors de la suppression de votre introduction",
|
||||
type: "error",
|
||||
position: "top-center",
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,13 +6,7 @@ import { useAuthedApi } from "./auth";
|
|||
export const useInviteUser = () => {
|
||||
const api = useAuthedApi();
|
||||
const { mutate, isPending } = useMutation({
|
||||
mutationFn: async ({
|
||||
email,
|
||||
tablo_id,
|
||||
}: {
|
||||
email: string;
|
||||
tablo_id: string;
|
||||
}) => {
|
||||
mutationFn: async ({ email, tablo_id }: { email: string; tablo_id: string }) => {
|
||||
const { data } = await api.post("/api/v1/tablos/invite", {
|
||||
email,
|
||||
tablo_id,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { QueryKey, useMutation } from "@tanstack/react-query";
|
||||
import { queryClient, toast } from "@xtablo/shared";
|
||||
import { supabase } from "../lib/supabase";
|
||||
import { queryClient } from "@xtablo/shared";
|
||||
import { toast } from "@xtablo/shared";
|
||||
import { useUser } from "../providers/UserStoreProvider";
|
||||
import { useAuthedApi } from "./auth";
|
||||
|
||||
|
|
@ -13,13 +12,7 @@ export function useUpdateProfile() {
|
|||
const user = useUser();
|
||||
|
||||
const { mutate, isPending } = useMutation({
|
||||
mutationFn: async ({
|
||||
firstName,
|
||||
lastName,
|
||||
}: {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}) => {
|
||||
mutationFn: async ({ firstName, lastName }: { firstName: string; lastName: string }) => {
|
||||
// Build the name from first_name and last_name if provided
|
||||
const fullName = [firstName, lastName].filter(Boolean).join(" ");
|
||||
|
||||
|
|
@ -49,8 +42,7 @@ export function useUpdateProfile() {
|
|||
onError: () => {
|
||||
toast.add({
|
||||
title: "Erreur",
|
||||
description:
|
||||
"Une erreur est survenue lors de la mise à jour de votre profil",
|
||||
description: "Une erreur est survenue lors de la mise à jour de votre profil",
|
||||
type: "error",
|
||||
position: "top-center",
|
||||
});
|
||||
|
|
@ -102,10 +94,7 @@ export const useUploadAvatar = () => {
|
|||
};
|
||||
|
||||
// Upload to backend
|
||||
const response = await api.post(
|
||||
"/api/v1/users/profile/avatar",
|
||||
uploadRequest
|
||||
);
|
||||
const response = await api.post("/api/v1/users/profile/avatar", uploadRequest);
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new Error("Failed to upload avatar");
|
||||
|
|
@ -126,8 +115,7 @@ export const useUploadAvatar = () => {
|
|||
onError: (error: Error) => {
|
||||
toast.add({
|
||||
title: "Erreur",
|
||||
description:
|
||||
error.message || "Une erreur est survenue lors de l'upload",
|
||||
description: error.message || "Une erreur est survenue lors de l'upload",
|
||||
type: "error",
|
||||
position: "top-center",
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,9 +1,4 @@
|
|||
import {
|
||||
QueryClient,
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from "@tanstack/react-query";
|
||||
import { QueryClient, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { toast } from "@xtablo/shared";
|
||||
import { useAuthedApi } from "./auth";
|
||||
|
||||
|
|
@ -59,9 +54,7 @@ export function useTabloFile(tabloId: string, fileName: string) {
|
|||
return useQuery<TabloFile>({
|
||||
queryKey: ["tablo-file", tabloId, fileName],
|
||||
queryFn: async () => {
|
||||
const response = await api.get(
|
||||
`/api/v1/tablo-data/${tabloId}/${fileName}`
|
||||
);
|
||||
const response = await api.get(`/api/v1/tablo-data/${tabloId}/${fileName}`);
|
||||
if (response.status !== 200) {
|
||||
throw new Error("Failed to fetch file");
|
||||
}
|
||||
|
|
@ -78,9 +71,7 @@ export function useDownloadTabloFile() {
|
|||
return useMutation<void, Error, { tabloId: string; fileName: string }>({
|
||||
mutationFn: async ({ tabloId, fileName }) => {
|
||||
try {
|
||||
const response = await api.get(
|
||||
`/api/v1/tablo-data/${tabloId}/${fileName}`
|
||||
);
|
||||
const response = await api.get(`/api/v1/tablo-data/${tabloId}/${fileName}`);
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new Error("Failed to download file");
|
||||
|
|
@ -149,10 +140,7 @@ export function useCreateTabloFile() {
|
|||
{ tabloId: string; fileName: string; data: FileUploadRequest }
|
||||
>({
|
||||
mutationFn: async ({ tabloId, fileName, data }) => {
|
||||
const response = await api.post(
|
||||
`/api/v1/tablo-data/${tabloId}/${fileName}`,
|
||||
data
|
||||
);
|
||||
const response = await api.post(`/api/v1/tablo-data/${tabloId}/${fileName}`, data);
|
||||
if (response.status !== 200) {
|
||||
throw new Error("Failed to create file");
|
||||
}
|
||||
|
|
@ -197,10 +185,7 @@ export function useUpdateTabloFile() {
|
|||
{ tabloId: string; fileName: string; data: FileUploadRequest }
|
||||
>({
|
||||
mutationFn: async ({ tabloId, fileName, data }) => {
|
||||
const response = await api.put(
|
||||
`/api/v1/tablo-data/${tabloId}/${fileName}`,
|
||||
data
|
||||
);
|
||||
const response = await api.put(`/api/v1/tablo-data/${tabloId}/${fileName}`, data);
|
||||
if (response.status !== 200) {
|
||||
throw new Error("Failed to update file");
|
||||
}
|
||||
|
|
@ -239,15 +224,9 @@ export function useDeleteTabloFile() {
|
|||
const api = useAuthedApi();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
FileOperationResponse,
|
||||
Error,
|
||||
{ tabloId: string; fileName: string }
|
||||
>({
|
||||
return useMutation<FileOperationResponse, Error, { tabloId: string; fileName: string }>({
|
||||
mutationFn: async ({ tabloId, fileName }) => {
|
||||
const response = await api.delete(
|
||||
`/api/v1/tablo-data/${tabloId}/${fileName}`
|
||||
);
|
||||
const response = await api.delete(`/api/v1/tablo-data/${tabloId}/${fileName}`);
|
||||
if (response.status !== 200) {
|
||||
throw new Error("Failed to delete file");
|
||||
}
|
||||
|
|
@ -282,10 +261,7 @@ export function useDeleteTabloFile() {
|
|||
}
|
||||
|
||||
// Utility function to invalidate all tablo data queries for a specific tablo
|
||||
export const invalidateTabloData = (
|
||||
queryClient: QueryClient,
|
||||
tabloId: string
|
||||
) => {
|
||||
export const invalidateTabloData = (queryClient: QueryClient, tabloId: string) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["tablo-files", tabloId],
|
||||
});
|
||||
|
|
@ -314,11 +290,7 @@ export function useUploadTabloFile() {
|
|||
// Check if file exists first (unless overwrite is explicitly true)
|
||||
if (!overwrite) {
|
||||
try {
|
||||
const existingFile = queryClient.getQueryData([
|
||||
"tablo-file",
|
||||
tabloId,
|
||||
fileName,
|
||||
]);
|
||||
const existingFile = queryClient.getQueryData(["tablo-file", tabloId, fileName]);
|
||||
if (existingFile) {
|
||||
// File exists, use update
|
||||
return await updateFile.mutateAsync({ tabloId, fileName, data });
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { toast } from "@xtablo/shared";
|
||||
import { useUser } from "../providers/UserStoreProvider";
|
||||
import { Database } from "@xtablo/shared/types/database.types";
|
||||
import { RemoveNullFromObject } from "@xtablo/shared/types/removeNull";
|
||||
import { CreateTablo } from "@xtablo/shared/types/tablos.types";
|
||||
import { supabase } from "../lib/supabase";
|
||||
import { useUser } from "../providers/UserStoreProvider";
|
||||
import { useAuthedApi } from "./auth";
|
||||
|
||||
type Tablo = Database["public"]["Tables"]["tablos"];
|
||||
|
||||
type TabloUpdate = Tablo["Update"];
|
||||
|
||||
type UserTablo = RemoveNullFromObject<
|
||||
Database["public"]["Views"]["user_tablos"]["Row"]
|
||||
>;
|
||||
type UserTablo = RemoveNullFromObject<Database["public"]["Views"]["user_tablos"]["Row"]>;
|
||||
|
||||
// Fetch all tablos
|
||||
export const useTablosList = () => {
|
||||
|
|
@ -21,10 +19,7 @@ export const useTablosList = () => {
|
|||
return useQuery({
|
||||
queryKey: ["tablos"],
|
||||
queryFn: async () => {
|
||||
const { data, error } = await supabase
|
||||
.from("user_tablos")
|
||||
.select("*")
|
||||
.eq("user_id", user.id);
|
||||
const { data, error } = await supabase.from("user_tablos").select("*").eq("user_id", user.id);
|
||||
if (error) throw error;
|
||||
const tablos = data as UserTablo[];
|
||||
return tablos;
|
||||
|
|
@ -161,10 +156,7 @@ export const useGetAllTabloAccess = () => {
|
|||
const { data, isLoading, error } = useQuery({
|
||||
queryKey: ["tablo-access", user.id],
|
||||
queryFn: async () => {
|
||||
const { data } = await supabase
|
||||
.from("tablo_access")
|
||||
.select("*")
|
||||
.eq("user_id", user.id);
|
||||
const { data } = await supabase.from("tablo_access").select("*").eq("user_id", user.id);
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -30,8 +30,7 @@ export const useGenerateWebcalToken = () => {
|
|||
toast.add(
|
||||
{
|
||||
title: "URL de synchronisation générée",
|
||||
description:
|
||||
"Vous pouvez maintenant ajouter ce calendrier à votre application",
|
||||
description: "Vous pouvez maintenant ajouter ce calendrier à votre application",
|
||||
type: "success",
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { RouteObject } from "react-router-dom";
|
||||
import { AuthenticationGateway } from "../components/AuthenticationGateway";
|
||||
import { EventModal } from "../components/EventModal";
|
||||
import { Layout } from "../components/Layout";
|
||||
|
|
@ -20,7 +21,6 @@ import SettingsPage from "../pages/settings";
|
|||
import { SignUpPage } from "../pages/signup";
|
||||
import { TabloPage } from "../pages/tablo";
|
||||
import ChatProvider from "../providers/ChatProvider";
|
||||
import { RouteObject } from "react-router-dom";
|
||||
|
||||
export const routes: RouteObject[] = [
|
||||
// Protected routes
|
||||
|
|
|
|||
|
|
@ -301,8 +301,7 @@
|
|||
transform: translate(-50%, -50%) rotate(0deg) translateX(150px) rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate(-50%, -50%) rotate(360deg) translateX(150px)
|
||||
rotate(-360deg);
|
||||
transform: translate(-50%, -50%) rotate(360deg) translateX(150px) rotate(-360deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -311,8 +310,7 @@
|
|||
transform: translate(-50%, -50%) rotate(0deg) translateX(200px) rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate(-50%, -50%) rotate(-360deg) translateX(200px)
|
||||
rotate(360deg);
|
||||
transform: translate(-50%, -50%) rotate(-360deg) translateX(200px) rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -321,8 +319,7 @@
|
|||
transform: translate(-50%, -50%) rotate(0deg) translateX(100px) rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate(-50%, -50%) rotate(360deg) translateX(100px)
|
||||
rotate(-360deg);
|
||||
transform: translate(-50%, -50%) rotate(360deg) translateX(100px) rotate(-360deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -500,8 +497,7 @@
|
|||
transform: translate(-50%, -50%) rotate(0deg) translateX(250px) rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate(-50%, -50%) rotate(360deg) translateX(250px)
|
||||
rotate(-360deg);
|
||||
transform: translate(-50%, -50%) rotate(360deg) translateX(250px) rotate(-360deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -510,8 +506,7 @@
|
|||
transform: translate(-50%, -50%) rotate(0deg) translateX(120px) rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate(-50%, -50%) rotate(-360deg) translateX(120px)
|
||||
rotate(360deg);
|
||||
transform: translate(-50%, -50%) rotate(-360deg) translateX(120px) rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { QueryClientProvider } from "@tanstack/react-query";
|
||||
import { queryClient } from "@xtablo/shared";
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { App } from "./App";
|
||||
import { queryClient } from "@xtablo/shared";
|
||||
|
||||
import "stream-chat-react/dist/css/v2/index.css";
|
||||
import "@xtablo/ui/styles/globals.css";
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { CustomModal } from "@ui/components/CustomModal";
|
||||
import { LoadingSpinner } from "@ui/components/LoadingSpinner";
|
||||
import { useCreateTabloWithOwner } from "@xtablo/shared";
|
||||
import { useSession } from "@xtablo/shared/contexts/SessionContext";
|
||||
import { useTheme } from "@xtablo/shared/contexts/ThemeContext";
|
||||
import { useSignUpWithoutPassword } from "@xtablo/shared/hooks/auth";
|
||||
import { invalidatePublicSlots, TimeSlot, usePublicSlots } from "@xtablo/shared/hooks/public";
|
||||
import { EventInsertInTablo } from "@xtablo/shared/types/events.types";
|
||||
import { Button } from "@xtablo/ui/components/button";
|
||||
import { FieldError } from "@xtablo/ui/components/field";
|
||||
import { Input } from "@xtablo/ui/components/input";
|
||||
import { Label } from "@xtablo/ui/components/label";
|
||||
import { Strong, Text } from "@xtablo/ui/components/typography";
|
||||
import { useSession } from "@xtablo/shared/contexts/SessionContext";
|
||||
import { useTheme } from "@xtablo/shared/contexts/ThemeContext";
|
||||
import { useSignUpWithoutPassword } from "@xtablo/shared/hooks/auth";
|
||||
import { invalidatePublicSlots, TimeSlot, usePublicSlots } from "@xtablo/shared/hooks/public";
|
||||
import { useCreateTabloWithOwner } from "@xtablo/shared";
|
||||
import { useMaybeUser } from "../providers/UserStoreProvider";
|
||||
import { EventInsertInTablo } from "@xtablo/shared/types/events.types";
|
||||
import {
|
||||
CalendarIcon,
|
||||
ChevronLeftIcon,
|
||||
|
|
@ -27,7 +27,7 @@ import { useState } from "react";
|
|||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { api } from "../lib/api";
|
||||
import { supabase } from "../lib/supabase";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useMaybeUser } from "../providers/UserStoreProvider";
|
||||
|
||||
export function PublicBookingPage() {
|
||||
const { user_info, event_type_standard_name } = useParams<{
|
||||
|
|
@ -47,14 +47,12 @@ export function PublicBookingPage() {
|
|||
event_type_standard_name || ""
|
||||
);
|
||||
|
||||
const { mutateAsync: createTabloWithOwner } = useCreateTabloWithOwner(api,
|
||||
(data) => {
|
||||
queryClient.invalidateQueries({ queryKey: ["tablos"] });
|
||||
invalidatePublicSlots();
|
||||
navigate(`/chat/${data.id}`, { replace: true });
|
||||
navigate(0);
|
||||
}
|
||||
);
|
||||
const { mutateAsync: createTabloWithOwner } = useCreateTabloWithOwner(api, (data) => {
|
||||
queryClient.invalidateQueries({ queryKey: ["tablos"] });
|
||||
invalidatePublicSlots();
|
||||
navigate(`/chat/${data.id}`, { replace: true });
|
||||
navigate(0);
|
||||
});
|
||||
|
||||
const userProfile = publicSlots?.user;
|
||||
const eventType = publicSlots?.eventType;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { AvailabilityCard } from "@ui/components/AvailabilityCard";
|
||||
import { AvailabilityVisualization } from "@ui/components/AvailabilityVisualization";
|
||||
import { toast } from "@xtablo/shared";
|
||||
import { Button } from "@xtablo/ui/components/button";
|
||||
import { Card } from "@xtablo/ui/components/card";
|
||||
import { Card, CardContent } from "@xtablo/ui/components/card";
|
||||
import { Checkbox } from "@xtablo/ui/components/checkbox";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
|
|
@ -17,21 +19,18 @@ import {
|
|||
EmptyHeader,
|
||||
EmptyTitle,
|
||||
} from "@xtablo/ui/components/empty";
|
||||
import { Label } from "@xtablo/ui/components/label";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@xtablo/ui/components/tabs";
|
||||
import { Strong, Text, TypographyH3, TypographyMuted } from "@xtablo/ui/components/typography";
|
||||
import { Plus as PlusIcon, SaveIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { ExceptionModal } from "../components/ExceptionModal";
|
||||
import {
|
||||
DEFAULT_AVAILABILITIES,
|
||||
Exception,
|
||||
useAvailabilities,
|
||||
WeeklyAvailability,
|
||||
} from "../hooks/availabilities";
|
||||
import { toast } from "@xtablo/shared";
|
||||
import { Checkbox } from "@xtablo/ui/components/checkbox";
|
||||
import { Plus as PlusIcon, SaveIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { ExceptionModal } from "../components/ExceptionModal";
|
||||
import { CardContent } from "@xtablo/ui/components/card";
|
||||
import { Label } from "@xtablo/ui/components/label";
|
||||
|
||||
const DAYS_OF_WEEK = [0, 1, 2, 3, 4, 5, 6];
|
||||
const DAYS_OF_WEEK_DISPLAY = [
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { EventDetailsModal } from "@ui/components/EventDetailsModal";
|
||||
import { LoadingSpinner } from "@ui/components/LoadingSpinner";
|
||||
import { getTextColorFromTabloColor } from "@xtablo/shared";
|
||||
import { EventAndTablo } from "@xtablo/shared/types/events.types";
|
||||
import { Button } from "@xtablo/ui/components/button";
|
||||
import { ButtonGroup } from "@xtablo/ui/components/button-group";
|
||||
import { Input } from "@xtablo/ui/components/input";
|
||||
|
|
@ -11,14 +13,12 @@ import {
|
|||
SelectValue,
|
||||
} from "@xtablo/ui/components/select";
|
||||
import { Strong, Text, TypographyH3, TypographyMuted } from "@xtablo/ui/components/typography";
|
||||
import { useEventsByTablo } from "../hooks/events";
|
||||
import { useGetAllTabloAccess, useTablosList } from "../hooks/tablos";
|
||||
import { EventAndTablo } from "@xtablo/shared/types/events.types";
|
||||
import { getTextColorFromTabloColor } from "@xtablo/shared";
|
||||
import { Calendar as CalendarIcon, ChevronLeft, ChevronRight, SearchIcon } from "lucide-react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { useEventsByTablo } from "../hooks/events";
|
||||
import { useGetAllTabloAccess, useTablosList } from "../hooks/tablos";
|
||||
|
||||
type BookingStatus = "all" | "upcoming" | "past";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
import { ChannelPreview } from "@ui/components/ChannelPreview";
|
||||
import { CustomChannelHeader } from "@ui/components/CustomChannelHeader";
|
||||
import { useChannelFromUrl } from "../hooks/channel";
|
||||
import { useTablosList } from "../hooks/tablos";
|
||||
import { useUser } from "../providers/UserStoreProvider";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
Channel,
|
||||
|
|
@ -12,6 +9,9 @@ import {
|
|||
useChatContext,
|
||||
Window,
|
||||
} from "stream-chat-react";
|
||||
import { useChannelFromUrl } from "../hooks/channel";
|
||||
import { useTablosList } from "../hooks/tablos";
|
||||
import { useUser } from "../providers/UserStoreProvider";
|
||||
|
||||
export function ChatPage() {
|
||||
const user = useUser();
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { EventTypeModal } from "@ui/components/EventTypeModal";
|
||||
import { toast } from "@xtablo/shared";
|
||||
import { Button } from "@xtablo/ui/components/button";
|
||||
import { Text, TypographyH3, TypographyMuted } from "@xtablo/ui/components/typography";
|
||||
import { EventTypeConfig, useEventTypes } from "../hooks/event-types";
|
||||
import { toast } from "@xtablo/shared";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { EventTypeCard } from "../components/EventTypeCard";
|
||||
import { EventTypeConfig, useEventTypes } from "../hooks/event-types";
|
||||
|
||||
export function EventTypesPage() {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ import {
|
|||
} from "@xtablo/ui/components/select";
|
||||
import { Textarea } from "@xtablo/ui/components/textarea";
|
||||
import { Text, TypographyH3, TypographyMuted } from "@xtablo/ui/components/typography";
|
||||
import { useCreateFeedback } from "../hooks/feedback";
|
||||
import { ArrowLeftIcon, SendIcon } from "lucide-react";
|
||||
import React, { useState } from "react";
|
||||
import { Separator } from "react-aria-components";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useCreateFeedback } from "../hooks/feedback";
|
||||
|
||||
export interface FeedbackData {
|
||||
fd_type: "bug" | "feature" | "improvement" | "other";
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
import { Button } from "@xtablo/ui/components/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@xtablo/ui/components/card";
|
||||
import { useJoinTablo } from "../hooks/invite";
|
||||
import { toast } from "@xtablo/shared";
|
||||
import { useUser } from "../providers/UserStoreProvider";
|
||||
import { Button } from "@xtablo/ui/components/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@xtablo/ui/components/card";
|
||||
import { CheckCircle2Icon, XCircleIcon } from "lucide-react";
|
||||
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||
import { useJoinTablo } from "../hooks/invite";
|
||||
import { useUser } from "../providers/UserStoreProvider";
|
||||
|
||||
export const JoinPage = () => {
|
||||
const { tablo_name } = useParams<{ tablo_name: string }>();
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import { AnimatedBackground } from "@ui/components/AnimatedBackground";
|
||||
import { LoginWithGoogle } from "@ui/components/BrandButtons/LoginWithGoogle";
|
||||
import { useTheme } from "@xtablo/shared/contexts/ThemeContext";
|
||||
import { Button } from "@xtablo/ui/components/button";
|
||||
import { FieldError } from "@xtablo/ui/components/field";
|
||||
import { Input } from "@xtablo/ui/components/input";
|
||||
import { Label } from "@xtablo/ui/components/label";
|
||||
import { useTheme } from "@xtablo/shared/contexts/ThemeContext";
|
||||
import { useLoginEmail } from "../hooks/auth";
|
||||
import { MonitorIcon, MoonIcon, SunIcon } from "lucide-react";
|
||||
import { useRef, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { useLoginEmail } from "../hooks/auth";
|
||||
|
||||
export function LoginPage() {
|
||||
const redirectUrl = localStorage.getItem("redirectUrl");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
import { ImportICSModal } from "@ui/components/ImportICSModal";
|
||||
import { WebcalModal } from "@ui/components/WebcalModal";
|
||||
import { downloadICSFile, generateICSFromEvents } from "@xtablo/shared";
|
||||
import { EventAndTablo } from "@xtablo/shared/types/events.types";
|
||||
import { Button } from "@xtablo/ui/components/button";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
|
|
@ -7,15 +11,11 @@ import {
|
|||
SelectValue,
|
||||
} from "@xtablo/ui/components/select";
|
||||
import { TypographyH3 } from "@xtablo/ui/components/typography";
|
||||
import { Button } from "@xtablo/ui/components/button";
|
||||
import { WebcalModal } from "@ui/components/WebcalModal";
|
||||
import { useDeleteEvent, useEventsByTablo } from "../hooks/events";
|
||||
import { useGetAllTabloAccess, useTablosList } from "../hooks/tablos";
|
||||
import { EventAndTablo } from "@xtablo/shared/types/events.types";
|
||||
import { downloadICSFile, generateICSFromEvents } from "@xtablo/shared";
|
||||
import { Download, FolderInputIcon, PlusIcon, RefreshCcw } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Outlet, useNavigate, useParams } from "react-router-dom";
|
||||
import { useDeleteEvent, useEventsByTablo } from "../hooks/events";
|
||||
import { useGetAllTabloAccess, useTablosList } from "../hooks/tablos";
|
||||
|
||||
type ViewType = "month" | "week" | "day";
|
||||
|
||||
|
|
@ -43,7 +43,12 @@ export const PlanningPage = () => {
|
|||
|
||||
// Check if an event can be deleted (e.g., based on permissions, event status, etc.)
|
||||
const canDeleteEvent = (event: EventAndTablo) => {
|
||||
if (tabloAccess?.find((access: { tablo_id: string; is_admin: boolean }) => access.tablo_id === event.tablo_id && access.is_admin)) {
|
||||
if (
|
||||
tabloAccess?.find(
|
||||
(access: { tablo_id: string; is_admin: boolean }) =>
|
||||
access.tablo_id === event.tablo_id && access.is_admin
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
import { Button } from "@xtablo/ui/components/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@xtablo/ui/components/card";
|
||||
import { Input } from "@xtablo/ui/components/input";
|
||||
import { Label } from "@xtablo/ui/components/label";
|
||||
import { Textarea } from "@xtablo/ui/components/textarea";
|
||||
import { ImageCropDialog } from "@ui/components/ImageCropDialog";
|
||||
import { toast } from "@xtablo/shared";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@xtablo/ui/components/avatar";
|
||||
import { Button } from "@xtablo/ui/components/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@xtablo/ui/components/card";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
|
|
@ -12,14 +17,15 @@ import {
|
|||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@xtablo/ui/components/dialog";
|
||||
import { useUser } from "../providers/UserStoreProvider";
|
||||
import { useState, useRef } from "react";
|
||||
import { Input } from "@xtablo/ui/components/input";
|
||||
import { Label } from "@xtablo/ui/components/label";
|
||||
import { Textarea } from "@xtablo/ui/components/textarea";
|
||||
import { TypographyH3, TypographyMuted, TypographySmall } from "@xtablo/ui/components/typography";
|
||||
import { CameraIcon, Loader2Icon, Trash2Icon, UploadIcon } from "lucide-react";
|
||||
import { useRef, useState } from "react";
|
||||
import { useIntroduction } from "../hooks/intros";
|
||||
import { useRemoveAvatar, useUpdateProfile, useUploadAvatar } from "../hooks/profile";
|
||||
import { CameraIcon, Loader2Icon, Trash2Icon, UploadIcon } from "lucide-react";
|
||||
import { toast } from "@xtablo/shared";
|
||||
import { ImageCropDialog } from "@ui/components/ImageCropDialog";
|
||||
import { useUser } from "../providers/UserStoreProvider";
|
||||
|
||||
export default function SettingsPage() {
|
||||
const user = useUser();
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import { AnimatedBackground } from "@ui/components/AnimatedBackground";
|
||||
import { LoginWithGoogle } from "@ui/components/BrandButtons/LoginWithGoogle";
|
||||
import { useTheme } from "@xtablo/shared/contexts/ThemeContext";
|
||||
import { Button } from "@xtablo/ui/components/button";
|
||||
import { FieldError } from "@xtablo/ui/components/field";
|
||||
import { Input } from "@xtablo/ui/components/input";
|
||||
import { Label } from "@xtablo/ui/components/label";
|
||||
import { useTheme } from "@xtablo/shared/contexts/ThemeContext";
|
||||
import { useSignUp } from "../hooks/auth";
|
||||
import { MonitorIcon, MoonIcon, SunIcon } from "lucide-react";
|
||||
import { useRef, useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { useSignUp } from "../hooks/auth";
|
||||
|
||||
export function SignUpPage() {
|
||||
const navigate = useNavigate();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { DeleteTabloModal } from "@ui/components/DeleteTabloModal";
|
|||
import { LoadingSpinner } from "@ui/components/LoadingSpinner";
|
||||
import { TabloModal } from "@ui/components/TabloModal";
|
||||
import { TabloTutorial } from "@ui/components/TabloTutorial";
|
||||
import { TabloInsert, TabloUpdate, UserTablo } from "@xtablo/shared/types/tablos.types";
|
||||
import { Button } from "@xtablo/ui/components/button";
|
||||
import {
|
||||
Empty,
|
||||
|
|
@ -20,8 +21,6 @@ import {
|
|||
SelectValue,
|
||||
} from "@xtablo/ui/components/select";
|
||||
import { Text, TypographyH3, TypographyMuted } from "@xtablo/ui/components/typography";
|
||||
import { useCreateTablo, useDeleteTablo, useTablosList, useUpdateTablo } from "../hooks/tablos";
|
||||
import { TabloInsert, TabloUpdate, UserTablo } from "@xtablo/shared/types/tablos.types";
|
||||
import {
|
||||
CheckCircle2,
|
||||
Clock,
|
||||
|
|
@ -37,6 +36,7 @@ import {
|
|||
} from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { useCreateTablo, useDeleteTablo, useTablosList, useUpdateTablo } from "../hooks/tablos";
|
||||
|
||||
type FilterOption = {
|
||||
id: "all" | "todo" | "in_progress" | "done";
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { datadogRum } from "@datadog/browser-rum";
|
||||
import { routes } from "../lib/routes";
|
||||
import { initRum } from "../lib/rum";
|
||||
import { useEffect } from "react";
|
||||
import { matchRoutes, RouteMatch, useLocation } from "react-router-dom";
|
||||
import { routes } from "../lib/routes";
|
||||
import { initRum } from "../lib/rum";
|
||||
|
||||
function computeViewName(routeMatches: RouteMatch[]) {
|
||||
let viewName = "";
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useSession } from "@xtablo/shared/contexts/SessionContext";
|
||||
import { api } from "../lib/api";
|
||||
import { Tables } from "@xtablo/shared/types/database.types";
|
||||
import React from "react";
|
||||
import { createStore, StoreApi, useStore } from "zustand";
|
||||
import { LoadingSpinner } from "../components/LoadingSpinner";
|
||||
import { api } from "../lib/api";
|
||||
|
||||
export type User = Tables<"profiles"> & {
|
||||
streamToken: string | null;
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ import { RenderResult, render, waitFor } from "@testing-library/react";
|
|||
import userEvent from "@testing-library/user-event";
|
||||
import { SessionTestProvider } from "@xtablo/shared/contexts/SessionContext";
|
||||
import { ThemeProvider } from "@xtablo/shared/contexts/ThemeContext";
|
||||
import { TestUserStoreProvider } from "../providers/UserStoreProvider";
|
||||
import React from "react";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import { TestUserStoreProvider } from "../providers/UserStoreProvider";
|
||||
|
||||
const defaultUser = {
|
||||
id: "123",
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -26,14 +26,9 @@
|
|||
"paths": {
|
||||
"*": ["./*"],
|
||||
"@ui/*": ["./src/*"],
|
||||
"@xtablo/ui/*": ["../../packages/ui/src/*"],
|
||||
// "@external/*": ["./src/external/*"],
|
||||
// "@xtablo/ui/components": ["../../packages/ui-components/src"],
|
||||
// "@xtablo/ui/components/*": ["../../packages/ui-components/src/*"],
|
||||
// "@xtablo/shared": ["../../packages/shared/src"],
|
||||
// "@xtablo/shared/*": ["../../packages/shared/src/*"]
|
||||
"@xtablo/ui/*": ["../../packages/ui/src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src", "worker"],
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
|||
34
apps/main/turbo.json
Normal file
34
apps/main/turbo.json
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"$schema": "https://turbo.build/schema.json",
|
||||
"extends": ["//"],
|
||||
"tasks": {
|
||||
"build:staging": {
|
||||
"inputs": [
|
||||
"src/**",
|
||||
"tsconfig.json",
|
||||
"tsconfig.*.json",
|
||||
"vite.config.ts",
|
||||
"package.json",
|
||||
".env.staging",
|
||||
".env.*.staging"
|
||||
],
|
||||
"outputs": ["dist/**", "tsconfig.tsbuildinfo"],
|
||||
"outputLogs": "new-only",
|
||||
"env": ["NODE_ENV", "VITE_*"]
|
||||
},
|
||||
"build:prod": {
|
||||
"inputs": [
|
||||
"src/**",
|
||||
"tsconfig.json",
|
||||
"tsconfig.*.json",
|
||||
"vite.config.ts",
|
||||
"package.json",
|
||||
".env.production",
|
||||
".env.*.production"
|
||||
],
|
||||
"outputs": ["dist/**", "tsconfig.tsbuildinfo"],
|
||||
"outputLogs": "new-only",
|
||||
"env": ["NODE_ENV", "VITE_*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,16 @@
|
|||
/// <reference types="vitest" />
|
||||
|
||||
import { cloudflare } from "@cloudflare/vite-plugin";
|
||||
// import { cloudflare } from "@cloudflare/vite-plugin";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { visualizer } from "rollup-plugin-visualizer";
|
||||
import { defineConfig, type PluginOption } from "vite";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
import { cloudflare } from "@cloudflare/vite-plugin";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
visualizer() as PluginOption,
|
||||
tailwindcss(),
|
||||
tsconfigPaths(),
|
||||
cloudflare(),
|
||||
],
|
||||
plugins: [react(), visualizer() as PluginOption, tailwindcss(), tsconfigPaths(), cloudflare()],
|
||||
server: {
|
||||
cors: false,
|
||||
},
|
||||
|
|
|
|||
66
apps/main/worker-configuration.d.ts
vendored
66
apps/main/worker-configuration.d.ts
vendored
|
|
@ -1,5 +1,5 @@
|
|||
// @ts-nocheck
|
||||
/* biome-ignore-file */
|
||||
// biome-ignore-file: Generated file from Wrangler
|
||||
|
||||
/* eslint-disable */
|
||||
// Generated by Wrangler by running `wrangler types` (hash: a253eed42b727ba5d2d964ef20087016)
|
||||
|
|
@ -33,7 +33,7 @@ and limitations under the License.
|
|||
***************************************************************************** */
|
||||
/* eslint-disable */
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
declare var onmessage: never;
|
||||
declare let onmessage: never;
|
||||
/**
|
||||
* An abnormal event (called an exception) which occurs as a result of calling a method or accessing a property of a web API.
|
||||
*
|
||||
|
|
@ -91,7 +91,7 @@ declare abstract class WorkerGlobalScope extends EventTarget<WorkerGlobalScopeEv
|
|||
}
|
||||
/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console) */
|
||||
interface Console {
|
||||
"assert"(condition?: boolean, ...data: any[]): void;
|
||||
assert(condition?: boolean, ...data: any[]): void;
|
||||
/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/clear_static) */
|
||||
clear(): void;
|
||||
/* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/count_static) */
|
||||
|
|
@ -1650,7 +1650,7 @@ declare abstract class Body {
|
|||
*
|
||||
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response)
|
||||
*/
|
||||
declare var Response: {
|
||||
declare let Response: {
|
||||
prototype: Response;
|
||||
new (body?: BodyInit | null, init?: ResponseInit): Response;
|
||||
error(): Response;
|
||||
|
|
@ -1698,7 +1698,7 @@ type RequestInfo<CfHostMetadata = unknown, Cf = CfProperties<CfHostMetadata>> =
|
|||
*
|
||||
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request)
|
||||
*/
|
||||
declare var Request: {
|
||||
declare let Request: {
|
||||
prototype: Request;
|
||||
new <CfHostMetadata = unknown, Cf = CfProperties<CfHostMetadata>>(
|
||||
input: RequestInfo<CfProperties> | URL,
|
||||
|
|
@ -2891,7 +2891,7 @@ type WebSocketEventMap = {
|
|||
*
|
||||
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket)
|
||||
*/
|
||||
declare var WebSocket: {
|
||||
declare let WebSocket: {
|
||||
prototype: WebSocket;
|
||||
new (url: string, protocols?: string[] | string): WebSocket;
|
||||
readonly READY_STATE_CONNECTING: number;
|
||||
|
|
@ -8743,20 +8743,48 @@ declare namespace Rpc {
|
|||
| Headers;
|
||||
// Recursively rewrite all `Stubable` types with `Stub`s
|
||||
// prettier-ignore
|
||||
type Stubify<T> = T extends Stubable ? Stub<T> : T extends Map<infer K, infer V> ? Map<Stubify<K>, Stubify<V>> : T extends Set<infer V> ? Set<Stubify<V>> : T extends Array<infer V> ? Array<Stubify<V>> : T extends ReadonlyArray<infer V> ? ReadonlyArray<Stubify<V>> : T extends BaseType ? T : T extends {
|
||||
[key: string | number]: any;
|
||||
} ? {
|
||||
[K in keyof T]: Stubify<T[K]>;
|
||||
} : T;
|
||||
type Stubify<T> = T extends Stubable
|
||||
? Stub<T>
|
||||
: T extends Map<infer K, infer V>
|
||||
? Map<Stubify<K>, Stubify<V>>
|
||||
: T extends Set<infer V>
|
||||
? Set<Stubify<V>>
|
||||
: T extends Array<infer V>
|
||||
? Array<Stubify<V>>
|
||||
: T extends ReadonlyArray<infer V>
|
||||
? ReadonlyArray<Stubify<V>>
|
||||
: T extends BaseType
|
||||
? T
|
||||
: T extends {
|
||||
[key: string | number]: any;
|
||||
}
|
||||
? {
|
||||
[K in keyof T]: Stubify<T[K]>;
|
||||
}
|
||||
: T;
|
||||
// Recursively rewrite all `Stub<T>`s with the corresponding `T`s.
|
||||
// Note we use `StubBase` instead of `Stub` here to avoid circular dependencies:
|
||||
// `Stub` depends on `Provider`, which depends on `Unstubify`, which would depend on `Stub`.
|
||||
// prettier-ignore
|
||||
type Unstubify<T> = T extends StubBase<infer V> ? V : T extends Map<infer K, infer V> ? Map<Unstubify<K>, Unstubify<V>> : T extends Set<infer V> ? Set<Unstubify<V>> : T extends Array<infer V> ? Array<Unstubify<V>> : T extends ReadonlyArray<infer V> ? ReadonlyArray<Unstubify<V>> : T extends BaseType ? T : T extends {
|
||||
[key: string | number]: unknown;
|
||||
} ? {
|
||||
[K in keyof T]: Unstubify<T[K]>;
|
||||
} : T;
|
||||
type Unstubify<T> = T extends StubBase<infer V>
|
||||
? V
|
||||
: T extends Map<infer K, infer V>
|
||||
? Map<Unstubify<K>, Unstubify<V>>
|
||||
: T extends Set<infer V>
|
||||
? Set<Unstubify<V>>
|
||||
: T extends Array<infer V>
|
||||
? Array<Unstubify<V>>
|
||||
: T extends ReadonlyArray<infer V>
|
||||
? ReadonlyArray<Unstubify<V>>
|
||||
: T extends BaseType
|
||||
? T
|
||||
: T extends {
|
||||
[key: string | number]: unknown;
|
||||
}
|
||||
? {
|
||||
[K in keyof T]: Unstubify<T[K]>;
|
||||
}
|
||||
: T;
|
||||
type UnstubifyAll<A extends any[]> = {
|
||||
[I in keyof A]: Unstubify<A[I]>;
|
||||
};
|
||||
|
|
@ -8772,7 +8800,11 @@ declare namespace Rpc {
|
|||
// Technically, we use custom thenables here, but they quack like `Promise`s.
|
||||
// Intersecting with `(Maybe)Provider` allows pipelining.
|
||||
// prettier-ignore
|
||||
type Result<R> = R extends Stubable ? Promise<Stub<R>> & Provider<R> : R extends Serializable<R> ? Promise<Stubify<R> & MaybeDisposable<R>> & MaybeProvider<R> : never;
|
||||
type Result<R> = R extends Stubable
|
||||
? Promise<Stub<R>> & Provider<R>
|
||||
: R extends Serializable<R>
|
||||
? Promise<Stubify<R> & MaybeDisposable<R>> & MaybeProvider<R>
|
||||
: never;
|
||||
// Type for method or property on an RPC interface.
|
||||
// For methods, unwrap `Stub`s in parameters, and rewrite returns to be `Result`s.
|
||||
// Unwrapping `Stub`s allows calling with `Stubable` arguments.
|
||||
|
|
|
|||
2
apps/main/worker/index.d.ts
vendored
2
apps/main/worker/index.d.ts
vendored
|
|
@ -1,4 +1,4 @@
|
|||
declare const _default: {
|
||||
fetch(request: any): Response;
|
||||
fetch(request: any): Response;
|
||||
};
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// @ts-nocheck
|
||||
/* biome-ignore-file */
|
||||
// biome-ignore-file: Worker entry point with dynamic imports
|
||||
/* eslint-disable */
|
||||
|
||||
export default {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
"scripts": {
|
||||
"build": "turbo build",
|
||||
"build:apps": "turbo build --filter='./apps/*'",
|
||||
"build:staging": "turbo build:staging --filter=@xtablo/main",
|
||||
"build:prod": "turbo build:prod --filter=@xtablo/main",
|
||||
"dev": "turbo dev",
|
||||
"dev:main": "turbo dev --filter=@xtablo/main",
|
||||
"dev:external": "turbo dev --filter=@xtablo/external",
|
||||
|
|
|
|||
40
packages/shared/biome.json
Normal file
40
packages/shared/biome.json
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.2.5/schema.json",
|
||||
"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": false },
|
||||
"files": {
|
||||
"ignoreUnknown": true
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"formatWithErrors": false,
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 2,
|
||||
"lineEnding": "lf",
|
||||
"lineWidth": 100,
|
||||
"attributePosition": "auto"
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"jsxQuoteStyle": "double",
|
||||
"quoteProperties": "asNeeded",
|
||||
"trailingCommas": "es5",
|
||||
"semicolons": "always",
|
||||
"arrowParentheses": "always",
|
||||
"bracketSameLine": false,
|
||||
"quoteStyle": "double",
|
||||
"attributePosition": "auto",
|
||||
"bracketSpacing": true
|
||||
}
|
||||
},
|
||||
"json": {
|
||||
"parser": { "allowComments": true, "allowTrailingCommas": false },
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 2,
|
||||
"lineEnding": "lf",
|
||||
"lineWidth": 100,
|
||||
"trailingCommas": "none"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -46,4 +46,3 @@
|
|||
"typescript": "^5.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Session, SupabaseClient, User } from "@supabase/supabase-js";
|
||||
import type { Session, SupabaseClient, User } from "@supabase/supabase-js";
|
||||
import { createContext, useContext, useEffect, useState } from "react";
|
||||
|
||||
const SessionContext = createContext<{
|
||||
|
|
@ -15,7 +15,7 @@ export const useSession = () => {
|
|||
return context;
|
||||
};
|
||||
|
||||
type Props = { supabase: SupabaseClient, children: React.ReactNode };
|
||||
type Props = { supabase: SupabaseClient; children: React.ReactNode };
|
||||
|
||||
export const SessionProvider = ({ supabase, children }: Props) => {
|
||||
const [session, setSession] = useState<Session | null>(null);
|
||||
|
|
@ -33,7 +33,7 @@ export const SessionProvider = ({ supabase, children }: Props) => {
|
|||
return <SessionContext.Provider value={{ session }}>{children}</SessionContext.Provider>;
|
||||
};
|
||||
|
||||
type TestProps = { children: React.ReactNode, testUser?: User };
|
||||
type TestProps = { children: React.ReactNode; testUser?: User };
|
||||
|
||||
export const SessionTestProvider = ({ children, testUser }: TestProps) => {
|
||||
const session = testUser
|
||||
|
|
|
|||
|
|
@ -1,20 +1,16 @@
|
|||
import { useState } from "react";
|
||||
import type { Session, SupabaseClient, User as SupabaseUser } from "@supabase/supabase-js";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { toast } from "../lib/toast";
|
||||
import type { AxiosInstance } from "axios";
|
||||
import { useState } from "react";
|
||||
import { match } from "ts-pattern";
|
||||
import { SupabaseClient, User as SupabaseUser } from "@supabase/supabase-js";
|
||||
import { Session } from "@supabase/supabase-js";
|
||||
import { AxiosInstance } from "axios";
|
||||
import { toast } from "../lib/toast";
|
||||
|
||||
interface AuthResponse {
|
||||
user: SupabaseUser | null;
|
||||
session: Session | null;
|
||||
}
|
||||
|
||||
export function useSignUpWithoutPassword(
|
||||
supabase: SupabaseClient,
|
||||
api: AxiosInstance
|
||||
) {
|
||||
export function useSignUpWithoutPassword(supabase: SupabaseClient, api: AxiosInstance) {
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
const { signUpToStream } = useSignUpToStream(api);
|
||||
const { mutateAsync, isPending } = useMutation<
|
||||
|
|
@ -25,8 +21,7 @@ export function useSignUpWithoutPassword(
|
|||
mutationFn: async (data: { email: string; name: string }) => {
|
||||
// Generate a temporary password for the user
|
||||
const tempPassword =
|
||||
Math.random().toString(36).slice(-8) +
|
||||
Math.random().toString(36).slice(-8);
|
||||
Math.random().toString(36).slice(-8) + Math.random().toString(36).slice(-8);
|
||||
|
||||
const { data: response, error } = await supabase.auth.signUp({
|
||||
email: data.email.trim(),
|
||||
|
|
@ -65,7 +60,7 @@ export function useSignUpWithoutPassword(
|
|||
|
||||
match(error.code)
|
||||
.with("user_already_exists", () => {
|
||||
errMap["email"] = "Cette adresse email est déjà utilisée";
|
||||
errMap.email = "Cette adresse email est déjà utilisée";
|
||||
})
|
||||
.otherwise(() => {
|
||||
toast.add(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useQuery } from "@tanstack/react-query";
|
||||
import type { AxiosInstance } from "axios";
|
||||
import { queryClient } from "../lib/api";
|
||||
import { AxiosInstance } from "axios";
|
||||
|
||||
type EventTypeConfig = {
|
||||
name: string;
|
||||
|
|
@ -23,11 +23,7 @@ export type TimeSlot = {
|
|||
available: boolean;
|
||||
};
|
||||
|
||||
export function usePublicSlots(
|
||||
api: AxiosInstance,
|
||||
shortUserId: string,
|
||||
standardName: string
|
||||
) {
|
||||
export function usePublicSlots(api: AxiosInstance, shortUserId: string, standardName: string) {
|
||||
return useQuery<{
|
||||
user: { name: string };
|
||||
eventType: EventTypeConfig;
|
||||
|
|
@ -36,9 +32,7 @@ export function usePublicSlots(
|
|||
}>({
|
||||
queryKey: ["public-slots", shortUserId, standardName],
|
||||
queryFn: async () => {
|
||||
const res = await api.get(
|
||||
`/api/public/slots/${shortUserId}/${standardName}`
|
||||
);
|
||||
const res = await api.get(`/api/public/slots/${shortUserId}/${standardName}`);
|
||||
if (res.status !== 200) {
|
||||
throw new Error("Failed to fetch public slots");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { AxiosInstance } from "axios";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { CreateTablo } from "../types/tablos.types";
|
||||
import { EventInsertInTablo } from "../types/events.types";
|
||||
import type { AxiosInstance } from "axios";
|
||||
import { toast } from "../lib/toast";
|
||||
import type { EventInsertInTablo } from "../types/events.types";
|
||||
import type { CreateTablo } from "../types/tablos.types";
|
||||
|
||||
// Create tablo with owner
|
||||
export const useCreateTabloWithOwner = (
|
||||
|
|
|
|||
|
|
@ -1,25 +1,21 @@
|
|||
// Export contexts
|
||||
export * from "./contexts/SessionContext";
|
||||
export * from "./contexts/ThemeContext";
|
||||
|
||||
// Export hooks
|
||||
export * from "./hooks/auth";
|
||||
export * from "./hooks/public";
|
||||
export * from "./hooks/tablos";
|
||||
export * from "./hooks/useClickOutside";
|
||||
export * from "./lib/api";
|
||||
// Export lib
|
||||
export * from "./lib/cn";
|
||||
export * from "./lib/supabase";
|
||||
export * from "./lib/toast";
|
||||
// Export types
|
||||
export * from "./types/database.types";
|
||||
export * from "./types/events.types";
|
||||
export * from "./types/kanban.types";
|
||||
export * from "./types/removeNull";
|
||||
export * from "./types/tablos.types";
|
||||
|
||||
// Export hooks
|
||||
export * from "./hooks/auth";
|
||||
export * from "./hooks/public";
|
||||
export * from "./hooks/tablos";
|
||||
export * from "./hooks/useClickOutside";
|
||||
|
||||
// Export utils
|
||||
export * from "./utils/helpers";
|
||||
|
||||
// Export lib
|
||||
export * from "./lib/cn";
|
||||
export * from "./lib/api";
|
||||
export * from "./lib/toast";
|
||||
export * from "./lib/supabase";
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,5 @@
|
|||
import { Tables, TablesInsert, TablesUpdate } from "../types/database.types";
|
||||
import { RemoveNullFromObject } from "../types/removeNull";
|
||||
import type { Tables, TablesInsert, TablesUpdate } from "../types/database.types";
|
||||
import type { RemoveNullFromObject } from "../types/removeNull";
|
||||
|
||||
export type Event = RemoveNullFromObject<Tables<"events">, "created_at" | "end_time">;
|
||||
export type EventInsert = TablesInsert<"events">;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Database } from "../types/database.types";
|
||||
import { EventInsertInTablo } from "../types/events.types";
|
||||
import { RemoveNullFromObject } from "../types/removeNull";
|
||||
import type { Database } from "../types/database.types";
|
||||
import type { EventInsertInTablo } from "../types/events.types";
|
||||
import type { RemoveNullFromObject } from "../types/removeNull";
|
||||
|
||||
export type UserTablo = RemoveNullFromObject<
|
||||
Database["public"]["Views"]["user_tablos"]["Row"],
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Database } from "../types/database.types";
|
||||
import { EventAndTablo } from "../types/events.types";
|
||||
import jsPDF from "jspdf";
|
||||
import type { Database } from "../types/database.types";
|
||||
import type { EventAndTablo } from "../types/events.types";
|
||||
|
||||
export const calculateTax = (amount: number, taxRate: number) => {
|
||||
return (amount * taxRate) / 100;
|
||||
|
|
@ -30,16 +30,8 @@ export const exportDevisToPdf = (devis: Devis) => {
|
|||
doc.setFontSize(12);
|
||||
doc.text(`Client: ${devis.client_email}`, 20, 40);
|
||||
|
||||
doc.text(
|
||||
`Date: ${new Date(devis.date).toLocaleDateString("fr-FR")}`,
|
||||
140,
|
||||
40
|
||||
);
|
||||
doc.text(
|
||||
`Date d'échéance: ${new Date(devis.due_date).toLocaleDateString("fr-FR")}`,
|
||||
140,
|
||||
48
|
||||
);
|
||||
doc.text(`Date: ${new Date(devis.date).toLocaleDateString("fr-FR")}`, 140, 40);
|
||||
doc.text(`Date d'échéance: ${new Date(devis.due_date).toLocaleDateString("fr-FR")}`, 140, 48);
|
||||
|
||||
// --- Amounts ---
|
||||
const startYAmounts = 70;
|
||||
|
|
@ -143,10 +135,7 @@ export const generateICSFromEvents = (
|
|||
return icsContent;
|
||||
};
|
||||
|
||||
export const downloadICSFile = (
|
||||
icsContent: string,
|
||||
filename: string = "planning.ics"
|
||||
) => {
|
||||
export const downloadICSFile = (icsContent: string, filename: string = "planning.ics") => {
|
||||
const blob = new Blob([icsContent], { type: "text/calendar;charset=utf-8" });
|
||||
const link = document.createElement("a");
|
||||
link.href = URL.createObjectURL(blob);
|
||||
|
|
@ -215,11 +204,7 @@ export const parseICSFile = (icsContent: string): ParsedICSEvent[] => {
|
|||
if (line === "BEGIN:VEVENT") {
|
||||
currentEvent = {};
|
||||
} else if (line === "END:VEVENT" && currentEvent) {
|
||||
if (
|
||||
currentEvent.title &&
|
||||
currentEvent.start_date &&
|
||||
currentEvent.start_time
|
||||
) {
|
||||
if (currentEvent.title && currentEvent.start_date && currentEvent.start_time) {
|
||||
events.push(currentEvent as ParsedICSEvent);
|
||||
}
|
||||
currentEvent = null;
|
||||
|
|
|
|||
|
|
@ -1,22 +1,21 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"esModuleInterop": true,
|
||||
"incremental": false,
|
||||
"isolatedModules": true,
|
||||
"lib": ["es2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"moduleResolution": "bundler",
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "ES2022",
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"esModuleInterop": true,
|
||||
"incremental": false,
|
||||
"isolatedModules": true,
|
||||
"lib": ["es2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"moduleResolution": "bundler",
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "ES2022",
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
||||
|
|
|
|||
3
packages/ui/.biomeignore
Normal file
3
packages/ui/.biomeignore
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Tailwind CSS v4 files (uses custom at-rules not recognized by Biome)
|
||||
src/styles/globals.css
|
||||
|
||||
50
packages/ui/biome.json
Normal file
50
packages/ui/biome.json
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.2.5/schema.json",
|
||||
"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": false },
|
||||
"files": {
|
||||
"ignoreUnknown": true,
|
||||
"experimentalScannerIgnores": ["**/globals.css"]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"formatWithErrors": false,
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 2,
|
||||
"lineEnding": "lf",
|
||||
"lineWidth": 100,
|
||||
"attributePosition": "auto"
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"jsxQuoteStyle": "double",
|
||||
"quoteProperties": "asNeeded",
|
||||
"trailingCommas": "es5",
|
||||
"semicolons": "always",
|
||||
"arrowParentheses": "always",
|
||||
"bracketSameLine": false,
|
||||
"quoteStyle": "double",
|
||||
"attributePosition": "auto",
|
||||
"bracketSpacing": true
|
||||
}
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"suspicious": {
|
||||
"noUnknownAtRules": "off"
|
||||
}
|
||||
}
|
||||
},
|
||||
"json": {
|
||||
"parser": { "allowComments": true, "allowTrailingCommas": false },
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 2,
|
||||
"lineEnding": "lf",
|
||||
"lineWidth": 100,
|
||||
"trailingCommas": "none"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -56,4 +56,3 @@
|
|||
"typescript": "^5.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import * as AvatarPrimitive from "@radix-ui/react-avatar";
|
||||
import { cn } from "@xtablo/shared";
|
||||
import * as React from "react";
|
||||
import type * as React from "react";
|
||||
|
||||
function Avatar({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Root>) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cn } from "@xtablo/shared";
|
||||
import { cva } from "class-variance-authority";
|
||||
import * as React from "react";
|
||||
import type * as React from "react";
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { Separator } from "./separator";
|
||||
|
||||
import { cn } from "@xtablo/shared";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { Separator } from "./separator";
|
||||
|
||||
const buttonGroupVariants = cva(
|
||||
"flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
"use client";
|
||||
|
||||
import { Button, buttonVariants } from "./button";
|
||||
import { cn } from "@xtablo/shared";
|
||||
import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker";
|
||||
import { type DayButton, DayPicker, getDefaultClassNames } from "react-day-picker";
|
||||
import { Button, buttonVariants } from "./button";
|
||||
|
||||
function Calendar({
|
||||
className,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { cn } from "@xtablo/shared";
|
||||
import * as React from "react";
|
||||
import type * as React from "react";
|
||||
|
||||
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
||||
import { CheckIcon } from "lucide-react";
|
||||
|
||||
import { cn } from "@xtablo/shared";
|
||||
import { CheckIcon } from "lucide-react";
|
||||
import type * as React from "react";
|
||||
|
||||
function Checkbox({ className, ...props }: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { cn } from "@xtablo/shared";
|
||||
import { useCopyToClipboard } from "../hooks/use-clipboard";
|
||||
import { Check, Copy } from "lucide-react";
|
||||
import React from "react";
|
||||
import { Button, ButtonProps } from "./button";
|
||||
import { useCopyToClipboard } from "../hooks/use-clipboard";
|
||||
import { Button, type ButtonProps } from "./button";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip";
|
||||
|
||||
export type ClipboardProps = {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
"use client";
|
||||
|
||||
import { CalendarDate } from "@internationalized/date";
|
||||
import { Button } from "./button";
|
||||
import { Calendar } from "./calendar";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "./popover";
|
||||
|
||||
import { cn } from "@xtablo/shared";
|
||||
import { format } from "date-fns";
|
||||
import { Calendar as CalendarIcon } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { Button } from "./button";
|
||||
import { Calendar } from "./calendar";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "./popover";
|
||||
|
||||
interface DateFieldProps {
|
||||
name?: string;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
"use client";
|
||||
|
||||
import { CalendarDate } from "@internationalized/date";
|
||||
import { Button } from "./button";
|
||||
import { Label } from "./label";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "./popover";
|
||||
import { cn } from "@xtablo/shared";
|
||||
import { Calendar as CalendarIcon, ChevronDownIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { Button } from "./button";
|
||||
import { Calendar } from "./calendar";
|
||||
import { Label } from "./label";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "./popover";
|
||||
|
||||
interface DatePickerProps {
|
||||
value?: Date | CalendarDate;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { Label } from "./label";
|
||||
import { Separator } from "./separator";
|
||||
|
||||
import { cn } from "@xtablo/shared";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { useMemo } from "react";
|
||||
import { Label } from "./label";
|
||||
import { Separator } from "./separator";
|
||||
|
||||
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
|
||||
return (
|
||||
|
|
@ -189,7 +188,7 @@ function FieldError({
|
|||
return null;
|
||||
}
|
||||
|
||||
if (errors?.length == 1) {
|
||||
if (errors?.length === 1) {
|
||||
return errors[0]?.message;
|
||||
}
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue