fix tickets add reort mail and design fixes

This commit is contained in:
Thomas Demirdjian
2025-10-29 15:36:12 +01:00
parent d1be3c19a2
commit ce285881ca
35 changed files with 1069 additions and 202 deletions
+16 -3
View File
@@ -1,26 +1,38 @@
import { Portal } from "@gorhom/portal";
import { BlurView } from "expo-blur";
import React from "react";
import React, { useCallback } from "react";
import { Platform, Pressable, StyleSheet, View } from "react-native";
import ActionSheet from "react-native-actions-sheet";
import ActionSheet, { SheetManager } from "react-native-actions-sheet";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { isWeb } from "../hooks/useLayoutType";
import { gutters, Palette } from "../styles";
const AppActionSheet = ({
id,
children,
webModal = false,
onClose = () => {},
...sheetProps
}) => {
const insets = useSafeAreaInsets();
const handleRequestClose = useCallback(() => {
if (id) {
Promise.resolve(SheetManager.hide(id))
.catch(() => {})
.finally(() => {
onClose?.();
});
return;
}
onClose?.();
}, [id, onClose]);
if (isWeb && webModal) {
return (
<Portal>
<View style={styles.webOverlay}>
<Pressable
onPress={onClose}
onPress={handleRequestClose}
accessibilityRole="button"
style={styles.webBackdrop}
/>
@@ -36,6 +48,7 @@ const AppActionSheet = ({
return (
<ActionSheet
id={id}
containerStyle={{ backgroundColor: Palette.tran }}
gestureEnabled
indicatorStyle={{
+21 -3
View File
@@ -6,6 +6,18 @@ import { FONT_FAMILY } from "../styles/Fonts";
import { size } from "../styles/Style";
import BorderGradient from "./BorderGradient/BorderGradient";
const HEIGHT_BY_SIZE = {
small: 40,
medium: 50,
large: 58,
};
const FONT_SIZE_BY_SIZE = {
small: 13,
medium: 15,
large: 17,
};
const BorderGradientButton = ({
title = "Jai déjà mes paroles",
onPress,
@@ -15,7 +27,13 @@ const BorderGradientButton = ({
tint = "dark",
disabled = false,
maxWidth = null,
size = "medium",
}) => {
const resolvedSize = HEIGHT_BY_SIZE[size] ? size : "medium";
const buttonHeight = HEIGHT_BY_SIZE[resolvedSize];
const fontSize = FONT_SIZE_BY_SIZE[resolvedSize];
const iconSize = resolvedSize === "small" ? 14 : 16;
return (
<Pressable
onPress={onPress}
@@ -34,7 +52,7 @@ const BorderGradientButton = ({
locations: [0, 1],
}}
style={{
height: 50,
height: buttonHeight,
borderRadius: 14,
borderWidth: 1,
zIndex: 1,
@@ -61,10 +79,10 @@ const BorderGradientButton = ({
gap: 11,
}}
>
{icon && <Image source={icon} style={size({ size: 16 })} />}
{icon && <Image source={icon} style={size({ size: iconSize })} />}
<Text
style={{
fontSize: 15,
fontSize,
color: Palette.white,
fontFamily: FONT_FAMILY.InterMedium,
...titleStyle,
+23 -2
View File
@@ -5,6 +5,18 @@ import { FONT_FAMILY } from "../styles/Fonts";
import { size } from "../styles/Style";
import { LinearGradient } from "./LinearGradient/LinearGradient";
const HEIGHT_BY_SIZE = {
small: 44,
medium: 50,
large: 58,
};
const FONT_SIZE_BY_SIZE = {
small: 14,
medium: 15,
large: 17,
};
const GradientButton = ({
title = "",
colors = ["#F94697", "#7023F7"],
@@ -14,7 +26,14 @@ const GradientButton = ({
icon,
disabled = false,
maxWidth = null,
size = "medium",
textStyle = {},
gradientStyle = {},
}) => {
const resolvedSize = HEIGHT_BY_SIZE[size] ? size : "medium";
const buttonHeight = HEIGHT_BY_SIZE[resolvedSize];
const fontSize = FONT_SIZE_BY_SIZE[resolvedSize];
return (
<Pressable
onPress={onPress}
@@ -30,9 +49,10 @@ const GradientButton = ({
style={{
...Style.containerCenter,
...Style.containerRow,
height: 50,
height: buttonHeight,
borderRadius: 14,
gap: 10,
...gradientStyle,
}}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
@@ -41,10 +61,11 @@ const GradientButton = ({
{icon && <Image source={icon} style={size({ size: 16 })} />}
<Text
style={{
fontSize: 15,
fontSize,
color: Palette.white,
fontFamily: FONT_FAMILY.InterMedium,
paddingHorizontal: 10,
...textStyle,
}}
>
{title}
@@ -166,6 +166,7 @@ const ListSelection = ({
const itemKey = buildItemKey(String(val ?? ""), cat);
const isHovered = isWeb && !disableHover && hoveredKey === itemKey;
const showHoverOutline = isHovered && !sel;
const showSelectionOutline = !isWeb && sel;
const baseContent = renderSimpleContent(item);
const headerContainerStyle = {
@@ -205,7 +206,7 @@ const ListSelection = ({
{withWebBlueGradientIfSelected(baseContent, sel)}
</CreateLyricsHeader>
{showHoverOutline && (
{(showHoverOutline || showSelectionOutline) && (
<View
pointerEvents="none"
style={{
+6 -2
View File
@@ -6,7 +6,11 @@ import { FONT_FAMILY } from "../../styles/Fonts";
import { Entypo } from "@expo/vector-icons";
import { openShareSheet } from "../../utils/shareSheet";
export default function ShareBtn({ style, onPress = null }) {
export default function ShareBtn({
style,
onPress = null,
label = "Partager lexpérience",
}) {
const handlePress = useCallback(() => {
if (typeof onPress === "function") {
onPress();
@@ -41,7 +45,7 @@ export default function ShareBtn({ style, onPress = null }) {
marginRight: 5,
}}
>
{"Partager lexpérience"}
{label}
</Text>
<Entypo name="share-alternative" size={16} color="white" />
</BlurView>
+3 -3
View File
@@ -30,7 +30,7 @@ const DeleteAccountModal = () => {
onPress: () => {
alert(
"Confirmation",
"Dernière vérification: supprimer définitivement votre profil ?",
"Dernière vérification : souhaitez-vous supprimer définitivement votre profil ?",
[
{ text: "Non", style: "cancel", onPress: () => {} },
{
@@ -85,8 +85,8 @@ const DeleteAccountModal = () => {
textAlign: "center",
}}
>
Es-tu sur de vouloir supprimer ton compte?{"\n"}Attention, cette
action est irréversible!
Es-tu sûr de vouloir supprimer ton compte ?{"\n"}Attention, cette
action est irréversible !
</Text>
</View>
<View style={{ width: "80%", alignSelf: "center", gap: 12 }}>
+268
View File
@@ -0,0 +1,268 @@
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { SheetManager } from "react-native-actions-sheet";
import {
KeyboardAvoidingView,
Modal,
Platform,
Pressable,
StyleSheet,
Text,
TextInput,
View,
} from "react-native";
import { useGlobal } from "reactn";
import BorderGradientButton from "../BorderGradientButton";
import GradientButton from "../GradientButton";
import { REPORT_OPTIONS } from "../../constants/reportOptions";
import { reportsRef, serverTimestamp } from "../../config/firebase";
import { Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
const SHEET_ID = "Report";
const ReportModal = ({ payload }) => {
const [, setTooltip] = useGlobal("_tooltip");
const [selectedReason, setSelectedReason] = useState(REPORT_OPTIONS[0].key);
const [details, setDetails] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const [isVisible, setIsVisible] = useState(true);
const resetForm = useCallback(() => {
setSelectedReason(REPORT_OPTIONS[0].key);
setDetails("");
setIsSubmitting(false);
}, []);
const targetContext = useMemo(() => {
return {
type: payload?.targetType || "music",
projectId: payload?.projectId || null,
playbackId: payload?.playbackId || null,
title: payload?.title || "",
ownerId: payload?.ownerId || null,
};
}, [payload]);
const closeSheet = useCallback(() => {
setIsVisible(false);
Promise.resolve(SheetManager.hide(SHEET_ID))
.catch(() => {})
.finally(() => {
resetForm();
});
}, [resetForm]);
useEffect(() => {
setIsVisible(true);
}, [payload]);
const handleSubmit = useCallback(async () => {
if (isSubmitting) return;
if (selectedReason === "other" && !details.trim()) return;
try {
setIsSubmitting(true);
const selectedOption = REPORT_OPTIONS.find(
(option) => option.key === selectedReason,
);
await reportsRef.add({
...targetContext,
reason: selectedReason,
reasonLabel: selectedOption?.label || selectedReason,
details: details.trim() || null,
platform: Platform.OS,
createdAt: serverTimestamp(),
});
setTooltip({
type: "success",
text: "Merci pour ton signalement, notre équipe va l'examiner.",
});
closeSheet();
} catch (error) {
setTooltip({
type: "error",
text:
error?.message ||
"Impossible d'envoyer le signalement pour le moment.",
});
} finally {
setIsSubmitting(false);
}
}, [
details,
closeSheet,
isSubmitting,
selectedReason,
setTooltip,
targetContext,
]);
const showDetailsField = selectedReason === "other";
return (
<Modal
visible={isVisible}
transparent
animationType="fade"
onRequestClose={closeSheet}
onDismiss={resetForm}
>
<View style={styles.overlay}>
<Pressable style={styles.backdrop} onPress={closeSheet} />
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : undefined}
style={styles.modalWrapper}
>
<View style={styles.card}>
<View style={{ gap: 8 }}>
<Text style={styles.heading}>Signaler ce contenu</Text>
{targetContext?.title ? (
<Text style={styles.subheading}>{targetContext.title}</Text>
) : null}
</View>
<View style={{ gap: 12 }}>
{REPORT_OPTIONS.map((option) => {
const isSelected = option.key === selectedReason;
return (
<Pressable
key={option.key}
style={[
styles.optionRow,
isSelected ? styles.optionRowSelected : null,
]}
onPress={() => setSelectedReason(option.key)}
>
<View
style={[
styles.radio,
isSelected ? styles.radioSelected : null,
]}
/>
<Text style={styles.optionLabel}>{option.label}</Text>
</Pressable>
);
})}
</View>
{showDetailsField ? (
<View style={{ gap: 6 }}>
<Text style={styles.fieldLabel}>Détails (optionnel)</Text>
<TextInput
style={styles.textArea}
placeholder="Explique-nous rapidement le problème…"
placeholderTextColor="rgba(255, 255, 255, 0.55)"
multiline
onChangeText={setDetails}
value={details}
/>
</View>
) : null}
<View style={{ gap: 12 }}>
<GradientButton
title={isSubmitting ? "Envoi en cours…" : "Envoyer"}
onPress={handleSubmit}
disabled={
isSubmitting ||
(selectedReason === "other" && details.trim().length === 0)
}
gradientStyle={{ minWidth: 160 }}
/>
<BorderGradientButton
title="Annuler"
onPress={closeSheet}
containerStyle={{ width: "100%" }}
/>
</View>
</View>
</KeyboardAvoidingView>
</View>
</Modal>
);
};
const styles = StyleSheet.create({
overlay: {
flex: 1,
backgroundColor: "rgba(0, 0, 0, 0.6)",
justifyContent: "center",
paddingHorizontal: 24,
position: "relative",
},
backdrop: {
...StyleSheet.absoluteFillObject,
},
modalWrapper: {
justifyContent: "center",
alignItems: "center",
},
card: {
gap: 20,
borderRadius: 24,
padding: 24,
backgroundColor: "rgba(18, 16, 24, 0.94)",
width: "100%",
maxWidth: 420,
alignSelf: "center",
},
heading: {
fontSize: 20,
color: Palette.white,
fontFamily: FONT_FAMILY.HelveticaNeueMedium,
textAlign: "center",
},
subheading: {
fontSize: 16,
color: Palette.transparentWhite,
fontFamily: FONT_FAMILY.InterMedium,
textAlign: "center",
},
optionRow: {
flexDirection: "row",
alignItems: "center",
paddingVertical: 10,
paddingHorizontal: 12,
borderRadius: 12,
backgroundColor: "rgba(255, 255, 255, 0.05)",
gap: 12,
},
optionRowSelected: {
backgroundColor: "rgba(251, 104, 168, 0.18)",
},
radio: {
width: 16,
height: 16,
borderRadius: 8,
borderWidth: 2,
borderColor: Palette.transparentWhite,
},
radioSelected: {
borderColor: Palette.primary,
backgroundColor: Palette.primary,
},
optionLabel: {
flex: 1,
fontSize: 15,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
},
fieldLabel: {
fontSize: 14,
color: Palette.transparentWhite,
fontFamily: FONT_FAMILY.InterMedium,
},
textArea: {
minHeight: 90,
borderRadius: 12,
borderWidth: 1,
borderColor: "rgba(255, 255, 255, 0.12)",
padding: 12,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
fontSize: 14,
textAlignVertical: "top",
backgroundColor: "rgba(0, 0, 0, 0.25)",
},
});
export default ReportModal;