feat(db): mesh data model — meshes, members, invites, audit log

- pgSchema "mesh" with 4 tables isolating the peer mesh domain
- Enums: visibility, transport, tier, role
- audit_log is metadata-only (E2E encryption enforced at broker/client)
- Cascade on mesh delete, soft-delete via archivedAt/revokedAt

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-04-04 21:19:32 +01:00
commit d3163a5bff
1384 changed files with 314925 additions and 0 deletions

View File

@@ -0,0 +1,112 @@
import { OTPInput } from "input-otp-native";
import React, { useEffect } from "react";
import { View, Text } from "react-native";
import Animated, {
useAnimatedStyle,
withRepeat,
withTiming,
withSequence,
useSharedValue,
} from "react-native-reanimated";
import { useCSSVariable } from "uniwind";
import { cn } from "@turbostarter/ui";
import type { SlotProps } from "input-otp-native";
function InputOTPGroup({
className,
...props
}: React.ComponentProps<typeof View>) {
return (
<View
data-slot="input-otp-group"
className={cn("flex-row items-center justify-center", className)}
{...props}
/>
);
}
function InputOTPSlot({
char,
isActive,
hasFakeCaret,
className,
index,
max,
...props
}: React.ComponentProps<typeof View> &
SlotProps & { index?: number; max?: number }) {
const isFirst = index === 0;
const isLast = index === (max ?? 6) - 1;
return (
<View
className={cn(
"border-input relative flex size-12 items-center justify-center border transition-all outline-none",
"dark:bg-input/30",
{
"border-ring": isActive,
"rounded-l-md": isFirst,
"rounded-r-md": isLast,
},
className,
)}
{...props}
>
{char !== null && (
<Text className="text-foreground font-sans-medium text-xl">{char}</Text>
)}
{hasFakeCaret && <FakeCaret />}
</View>
);
}
function FakeCaret() {
const opacity = useSharedValue(1);
const foregroundColor = useCSSVariable("--foreground");
useEffect(() => {
opacity.value = withRepeat(
withSequence(
withTiming(0, { duration: 500 }),
withTiming(1, { duration: 500 }),
),
-1,
true,
);
}, [opacity]);
const animatedStyle = useAnimatedStyle(() => ({
opacity: opacity.value,
}));
const baseStyle = {
width: 2,
height: 20,
borderRadius: 1,
...(foregroundColor && { backgroundColor: foregroundColor.toString() }),
};
return (
<View className="absolute h-full w-full items-center justify-center">
<Animated.View style={[baseStyle, animatedStyle]} />
</View>
);
}
function InputOTPSeparator({
className,
...props
}: React.ComponentProps<typeof View>) {
return (
<View
{...props}
className={cn("w-1 items-center justify-center", className)}
>
<View className="bg-muted-foreground h-0.5 w-1 rounded-sm" />
</View>
);
}
export { OTPInput as InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator };