@innspel/react-native 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { MaskRect, InnspelClient, OpenFeedbackOptions, InnspelStorage, SubmissionStatus, SubmissionType } from '@innspel/core';
2
- export { AttachmentInput, AttachmentOutcome, CaptureInput, ContextInput, InnspelClient, InnspelOptions, InnspelStorage, MySubmission, OpenFeedbackOptions, SubmissionStatusView } from '@innspel/core';
1
+ import { MaskRect, InnspelClient, OpenFeedbackOptions, ParticipationView, InnspelStorage, SubmissionStatus, SubmissionType } from '@innspel/core';
2
+ export { AttachmentInput, AttachmentOutcome, CaptureInput, ContextInput, InnspelClient, InnspelOptions, InnspelStorage, InvitationView, MyPrograms, MySubmission, OpenFeedbackOptions, ParticipationView, ProgramTask, ProgramView, SubmissionStatusView } from '@innspel/core';
3
3
  import React, { RefObject, ComponentRef } from 'react';
4
4
  import { View, StyleProp, ViewStyle } from 'react-native';
5
5
 
@@ -55,7 +55,7 @@ type CaptureRefFn = (ref: RefObject<ViewRef | null> | ViewRef, options: {
55
55
  declare function createScreenshotAdapter(captureRef: CaptureRefFn): ScreenshotAdapter;
56
56
 
57
57
  /**
58
- * tokens.ts — GENERERT fra theme/theme.ts (avtrykk 96a5d02e08c9).
58
+ * tokens.ts — GENERERT fra theme/theme.ts (avtrykk e9fb0cf68373).
59
59
  * Rediger aldri denne filen. Endre temaet i kilden og kjør
60
60
  * `node scripts/generate-tokens.ts`, som CI verifiserer med `--check`.
61
61
  *
@@ -364,6 +364,32 @@ declare function InnspelPrivate({ children, style }: {
364
364
  style?: StyleProp<ViewStyle>;
365
365
  }): React.JSX.Element;
366
366
 
367
+ /**
368
+ * Betaprogrammet i vertsappen: 3a banneret, 3b aksepten og deltakelsen med
369
+ * «Gå ut» (DD-36). Sett den der vertsappen vil ha invitasjonen — typisk øverst
370
+ * på hjemskjermen. Uten invitasjon og uten deltakelse rendrer den INGENTING;
371
+ * det er wireframens «tom → fanen skjules helt», ikke en tom tilstand.
372
+ *
373
+ * **Tokenet kommer fra verten** (DD-12). Plattformen sender aldri invitasjonen:
374
+ * leietakeren får `program.invitation_created` med tokenet, løser pseudonymet til
375
+ * sin egen bruker og sender lenken i sin egen kanal. Verten fanger tokenet fra
376
+ * dyplenken og gir det hit. Uten det viser banneret fortsatt invitasjonen — men
377
+ * «Bli med» er inaktiv, med lenken forklart, i stedet for å feile ved trykk.
378
+ *
379
+ * Oppgavene, «Hvordan gikk det?» og oppfølgingene er M3.2.
380
+ *
381
+ * @example
382
+ * ```tsx
383
+ * <InnspelProgram invitationToken={tokenFraDyplenke} />
384
+ * ```
385
+ */
386
+ declare function InnspelProgram({ invitationToken, onEndring, }: {
387
+ /** Engangs-tokenet fra leietakerens egen kanal, typisk fanget fra en dyplenke. */
388
+ invitationToken?: string;
389
+ /** Kalles når testeren blir med eller går ut — for vertens egen navigasjon. */
390
+ onEndring?: (deltakelse: ParticipationView | null) => void;
391
+ }): React.JSX.Element | null;
392
+
367
393
  /**
368
394
  * Formen på lagringspakkene i React Native-økosystemet: AsyncStorage, MMKV med
369
395
  * async-fasade, expo-secure-store. Alle tre har disse tre metodene.
@@ -525,6 +551,37 @@ declare const tekst: {
525
551
  readonly svarFra: (leietaker: string) => string;
526
552
  readonly svarFraGenerisk: "Svar";
527
553
  readonly sendNytt: "Send nytt innspill";
554
+ readonly invitasjonRegion: "Invitasjon til betaprogram";
555
+ readonly invitasjonHva: (navn: string) => string;
556
+ readonly invitasjonKontakter: (maks: number) => string;
557
+ readonly lesMer: "Les mer og bli med";
558
+ readonly avvisInvitasjon: "Avvis invitasjonen";
559
+ readonly akseptHva: "Hva testes";
560
+ /** DD-32: tallene på én linje, FØR forklaringen — N oppgaver · ca. M min hver · dato–dato · maks K kontakter. */
561
+ readonly akseptTall: (oppgaver: number, minutter: number, periode: string, kontakter: number) => string;
562
+ readonly akseptOppgaver: "Hva vi ber om";
563
+ readonly akseptOppgaveTid: (minutter: number) => string;
564
+ readonly akseptKjenteFeil: (antall: number) => string;
565
+ readonly akseptKjenteFeilUnder: "Disse trenger du ikke melde — vi vet om dem.";
566
+ readonly akseptInnsamling: "Hva som samles inn";
567
+ readonly akseptInnsamlingTekst: "Skjerm, appversjon, plattform, OS og hva du svarer. Aldri navn, e-post, telefon eller posisjon.";
568
+ readonly akseptVilkar: "Les vilkårene (åpnes i nettleser)";
569
+ readonly akseptVilkarHint: "Bare en lenke. Ingen avkryssing, ingen signatur — «Bli med» binder deg ikke til noe.";
570
+ readonly akseptIngenVilkar: "Ingen vilkår, ingen avtale — plattformen kjenner ikke navnet ditt og kan ikke inngå én.";
571
+ readonly akseptUtgang: "Du kan gå ut når som helst — herfra.";
572
+ readonly bliMed: "Bli med";
573
+ readonly bliMedHint: "Samtykket gjelder bare dette programmet.";
574
+ /** DD-12: tokenet er engangs og kom i leietakerens egen kanal — plattformen sender aldri. */
575
+ readonly bliMedUtenToken: "Åpne lenken vi sendte deg for å bli med.";
576
+ readonly akseptFeil: (arsak: string) => string;
577
+ readonly deltakelseRegion: "Ditt betaprogram";
578
+ readonly deltakelsePeriode: (periode: string, brukt: number, maks: number) => string;
579
+ readonly deltakelseOppgaverKommer: "Oppgavene kan gjøres når det passer deg.";
580
+ readonly gaaUt: "Gå ut av programmet";
581
+ readonly gaaUtTittel: "Gå ut av programmet?";
582
+ readonly gaaUtTekst: (navn: string) => string;
583
+ readonly bliVaerende: "Bli værende";
584
+ readonly gaaUtBekreft: "Gå ut";
528
585
  readonly tilbake: "Tilbake";
529
586
  readonly lukk: "Lukk";
530
587
  readonly venterPaaNett: "Venter på nett — sendes automatisk";
@@ -548,4 +605,4 @@ declare const lofte: {
548
605
  readonly avvist: "Vi har svart på denne.";
549
606
  };
550
607
 
551
- export { type AsyncStorageLike, type CaptureOptions, type CaptureRefFn, type InnspelColors, InnspelPrivate, InnspelProvider, type InnspelTheme, type PartialTheme, type ScreenshotAdapter, type ScreenshotCapture, type StatusVisning, type Tema, byggTema, createMemoryStorage, createScreenshotAdapter, createStorage, t as darkTokens, light, lofte, screenRules, statusVisning, tekst, useInnspel, useTema, venterVisning };
608
+ export { type AsyncStorageLike, type CaptureOptions, type CaptureRefFn, type InnspelColors, InnspelPrivate, InnspelProgram, InnspelProvider, type InnspelTheme, type PartialTheme, type ScreenshotAdapter, type ScreenshotCapture, type StatusVisning, type Tema, byggTema, createMemoryStorage, createScreenshotAdapter, createStorage, t as darkTokens, light, lofte, screenRules, statusVisning, tekst, useInnspel, useTema, venterVisning };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { MaskRect, InnspelClient, OpenFeedbackOptions, InnspelStorage, SubmissionStatus, SubmissionType } from '@innspel/core';
2
- export { AttachmentInput, AttachmentOutcome, CaptureInput, ContextInput, InnspelClient, InnspelOptions, InnspelStorage, MySubmission, OpenFeedbackOptions, SubmissionStatusView } from '@innspel/core';
1
+ import { MaskRect, InnspelClient, OpenFeedbackOptions, ParticipationView, InnspelStorage, SubmissionStatus, SubmissionType } from '@innspel/core';
2
+ export { AttachmentInput, AttachmentOutcome, CaptureInput, ContextInput, InnspelClient, InnspelOptions, InnspelStorage, InvitationView, MyPrograms, MySubmission, OpenFeedbackOptions, ParticipationView, ProgramTask, ProgramView, SubmissionStatusView } from '@innspel/core';
3
3
  import React, { RefObject, ComponentRef } from 'react';
4
4
  import { View, StyleProp, ViewStyle } from 'react-native';
5
5
 
@@ -55,7 +55,7 @@ type CaptureRefFn = (ref: RefObject<ViewRef | null> | ViewRef, options: {
55
55
  declare function createScreenshotAdapter(captureRef: CaptureRefFn): ScreenshotAdapter;
56
56
 
57
57
  /**
58
- * tokens.ts — GENERERT fra theme/theme.ts (avtrykk 96a5d02e08c9).
58
+ * tokens.ts — GENERERT fra theme/theme.ts (avtrykk e9fb0cf68373).
59
59
  * Rediger aldri denne filen. Endre temaet i kilden og kjør
60
60
  * `node scripts/generate-tokens.ts`, som CI verifiserer med `--check`.
61
61
  *
@@ -364,6 +364,32 @@ declare function InnspelPrivate({ children, style }: {
364
364
  style?: StyleProp<ViewStyle>;
365
365
  }): React.JSX.Element;
366
366
 
367
+ /**
368
+ * Betaprogrammet i vertsappen: 3a banneret, 3b aksepten og deltakelsen med
369
+ * «Gå ut» (DD-36). Sett den der vertsappen vil ha invitasjonen — typisk øverst
370
+ * på hjemskjermen. Uten invitasjon og uten deltakelse rendrer den INGENTING;
371
+ * det er wireframens «tom → fanen skjules helt», ikke en tom tilstand.
372
+ *
373
+ * **Tokenet kommer fra verten** (DD-12). Plattformen sender aldri invitasjonen:
374
+ * leietakeren får `program.invitation_created` med tokenet, løser pseudonymet til
375
+ * sin egen bruker og sender lenken i sin egen kanal. Verten fanger tokenet fra
376
+ * dyplenken og gir det hit. Uten det viser banneret fortsatt invitasjonen — men
377
+ * «Bli med» er inaktiv, med lenken forklart, i stedet for å feile ved trykk.
378
+ *
379
+ * Oppgavene, «Hvordan gikk det?» og oppfølgingene er M3.2.
380
+ *
381
+ * @example
382
+ * ```tsx
383
+ * <InnspelProgram invitationToken={tokenFraDyplenke} />
384
+ * ```
385
+ */
386
+ declare function InnspelProgram({ invitationToken, onEndring, }: {
387
+ /** Engangs-tokenet fra leietakerens egen kanal, typisk fanget fra en dyplenke. */
388
+ invitationToken?: string;
389
+ /** Kalles når testeren blir med eller går ut — for vertens egen navigasjon. */
390
+ onEndring?: (deltakelse: ParticipationView | null) => void;
391
+ }): React.JSX.Element | null;
392
+
367
393
  /**
368
394
  * Formen på lagringspakkene i React Native-økosystemet: AsyncStorage, MMKV med
369
395
  * async-fasade, expo-secure-store. Alle tre har disse tre metodene.
@@ -525,6 +551,37 @@ declare const tekst: {
525
551
  readonly svarFra: (leietaker: string) => string;
526
552
  readonly svarFraGenerisk: "Svar";
527
553
  readonly sendNytt: "Send nytt innspill";
554
+ readonly invitasjonRegion: "Invitasjon til betaprogram";
555
+ readonly invitasjonHva: (navn: string) => string;
556
+ readonly invitasjonKontakter: (maks: number) => string;
557
+ readonly lesMer: "Les mer og bli med";
558
+ readonly avvisInvitasjon: "Avvis invitasjonen";
559
+ readonly akseptHva: "Hva testes";
560
+ /** DD-32: tallene på én linje, FØR forklaringen — N oppgaver · ca. M min hver · dato–dato · maks K kontakter. */
561
+ readonly akseptTall: (oppgaver: number, minutter: number, periode: string, kontakter: number) => string;
562
+ readonly akseptOppgaver: "Hva vi ber om";
563
+ readonly akseptOppgaveTid: (minutter: number) => string;
564
+ readonly akseptKjenteFeil: (antall: number) => string;
565
+ readonly akseptKjenteFeilUnder: "Disse trenger du ikke melde — vi vet om dem.";
566
+ readonly akseptInnsamling: "Hva som samles inn";
567
+ readonly akseptInnsamlingTekst: "Skjerm, appversjon, plattform, OS og hva du svarer. Aldri navn, e-post, telefon eller posisjon.";
568
+ readonly akseptVilkar: "Les vilkårene (åpnes i nettleser)";
569
+ readonly akseptVilkarHint: "Bare en lenke. Ingen avkryssing, ingen signatur — «Bli med» binder deg ikke til noe.";
570
+ readonly akseptIngenVilkar: "Ingen vilkår, ingen avtale — plattformen kjenner ikke navnet ditt og kan ikke inngå én.";
571
+ readonly akseptUtgang: "Du kan gå ut når som helst — herfra.";
572
+ readonly bliMed: "Bli med";
573
+ readonly bliMedHint: "Samtykket gjelder bare dette programmet.";
574
+ /** DD-12: tokenet er engangs og kom i leietakerens egen kanal — plattformen sender aldri. */
575
+ readonly bliMedUtenToken: "Åpne lenken vi sendte deg for å bli med.";
576
+ readonly akseptFeil: (arsak: string) => string;
577
+ readonly deltakelseRegion: "Ditt betaprogram";
578
+ readonly deltakelsePeriode: (periode: string, brukt: number, maks: number) => string;
579
+ readonly deltakelseOppgaverKommer: "Oppgavene kan gjøres når det passer deg.";
580
+ readonly gaaUt: "Gå ut av programmet";
581
+ readonly gaaUtTittel: "Gå ut av programmet?";
582
+ readonly gaaUtTekst: (navn: string) => string;
583
+ readonly bliVaerende: "Bli værende";
584
+ readonly gaaUtBekreft: "Gå ut";
528
585
  readonly tilbake: "Tilbake";
529
586
  readonly lukk: "Lukk";
530
587
  readonly venterPaaNett: "Venter på nett — sendes automatisk";
@@ -548,4 +605,4 @@ declare const lofte: {
548
605
  readonly avvist: "Vi har svart på denne.";
549
606
  };
550
607
 
551
- export { type AsyncStorageLike, type CaptureOptions, type CaptureRefFn, type InnspelColors, InnspelPrivate, InnspelProvider, type InnspelTheme, type PartialTheme, type ScreenshotAdapter, type ScreenshotCapture, type StatusVisning, type Tema, byggTema, createMemoryStorage, createScreenshotAdapter, createStorage, t as darkTokens, light, lofte, screenRules, statusVisning, tekst, useInnspel, useTema, venterVisning };
608
+ export { type AsyncStorageLike, type CaptureOptions, type CaptureRefFn, type InnspelColors, InnspelPrivate, InnspelProgram, InnspelProvider, type InnspelTheme, type PartialTheme, type ScreenshotAdapter, type ScreenshotCapture, type StatusVisning, type Tema, byggTema, createMemoryStorage, createScreenshotAdapter, createStorage, t as darkTokens, light, lofte, screenRules, statusVisning, tekst, useInnspel, useTema, venterVisning };
package/dist/index.js CHANGED
@@ -546,6 +546,40 @@ var tekst = {
546
546
  svarFra: (leietaker) => `Svar fra ${leietaker}`,
547
547
  svarFraGenerisk: "Svar",
548
548
  sendNytt: "Send nytt innspill",
549
+ // 3a — invitasjonsbanneret (DD-12)
550
+ invitasjonRegion: "Invitasjon til betaprogram",
551
+ invitasjonHva: (navn) => `Vil du teste \xAB${navn}\xBB?`,
552
+ invitasjonKontakter: (maks) => `Vi tar kontakt maks ${maks} ganger. Du kan g\xE5 ut n\xE5r som helst.`,
553
+ lesMer: "Les mer og bli med",
554
+ avvisInvitasjon: "Avvis invitasjonen",
555
+ // 3b — aksepten (DD-13, DD-32, DD-37)
556
+ akseptHva: "Hva testes",
557
+ /** DD-32: tallene på én linje, FØR forklaringen — N oppgaver · ca. M min hver · dato–dato · maks K kontakter. */
558
+ akseptTall: (oppgaver, minutter, periode2, kontakter) => `${oppgaver} oppgaver \xB7 ca. ${minutter} min hver \xB7 ${periode2} \xB7 maks ${kontakter} kontakter`,
559
+ akseptOppgaver: "Hva vi ber om",
560
+ akseptOppgaveTid: (minutter) => `ca. ${minutter} min`,
561
+ akseptKjenteFeil: (antall) => `Kjente feil (${antall})`,
562
+ akseptKjenteFeilUnder: "Disse trenger du ikke melde \u2014 vi vet om dem.",
563
+ akseptInnsamling: "Hva som samles inn",
564
+ akseptInnsamlingTekst: "Skjerm, appversjon, plattform, OS og hva du svarer. Aldri navn, e-post, telefon eller posisjon.",
565
+ akseptVilkar: "Les vilk\xE5rene (\xE5pnes i nettleser)",
566
+ akseptVilkarHint: "Bare en lenke. Ingen avkryssing, ingen signatur \u2014 \xABBli med\xBB binder deg ikke til noe.",
567
+ akseptIngenVilkar: "Ingen vilk\xE5r, ingen avtale \u2014 plattformen kjenner ikke navnet ditt og kan ikke inng\xE5 \xE9n.",
568
+ akseptUtgang: "Du kan g\xE5 ut n\xE5r som helst \u2014 herfra.",
569
+ bliMed: "Bli med",
570
+ bliMedHint: "Samtykket gjelder bare dette programmet.",
571
+ /** DD-12: tokenet er engangs og kom i leietakerens egen kanal — plattformen sender aldri. */
572
+ bliMedUtenToken: "\xC5pne lenken vi sendte deg for \xE5 bli med.",
573
+ akseptFeil: (arsak) => `Kunne ikke bli med: ${arsak}`,
574
+ // 3c (M3.1-delen) — deltakelsen og «Gå ut» (DD-36)
575
+ deltakelseRegion: "Ditt betaprogram",
576
+ deltakelsePeriode: (periode2, brukt, maks) => `${periode2} \xB7 ${brukt} av ${maks} kontakter brukt`,
577
+ deltakelseOppgaverKommer: "Oppgavene kan gj\xF8res n\xE5r det passer deg.",
578
+ gaaUt: "G\xE5 ut av programmet",
579
+ gaaUtTittel: "G\xE5 ut av programmet?",
580
+ gaaUtTekst: (navn) => `Du tas ut av testgruppen med en gang. \xAB${navn}\xBB sl\xE5s av for deg, og vi tar ikke kontakt igjen. Det du allerede har svart, beholdes.`,
581
+ bliVaerende: "Bli v\xE6rende",
582
+ gaaUtBekreft: "G\xE5 ut",
549
583
  // Felles
550
584
  tilbake: "Tilbake",
551
585
  lukk: "Lukk",
@@ -1397,7 +1431,8 @@ function Widget({
1397
1431
  const [rotStorrelse, setRotStorrelse] = useState3({ width: 1, height: 1 });
1398
1432
  const [komponerer, setKomponerer] = useState3(false);
1399
1433
  const [vedlegg, setVedlegg] = useState3();
1400
- const skjermbildeMulig = client.settings.screenshotsEnabled && skjermbilde?.adapter != null;
1434
+ const [innstillinger, setInnstillinger] = useState3({ ...client.settings });
1435
+ const skjermbildeMulig = innstillinger.screenshotsEnabled && skjermbilde?.adapter != null;
1401
1436
  const nullstill = useCallback2(() => {
1402
1437
  setSteg("valg");
1403
1438
  setSpor("feil");
@@ -1421,6 +1456,7 @@ function Widget({
1421
1456
  if (!synlig) return;
1422
1457
  client.track({ name: "widget_opened" });
1423
1458
  void client.queue.flush().catch(() => void 0);
1459
+ void client.loadSettings().then((s) => setInnstillinger({ ...s })).catch(() => void 0);
1424
1460
  if (apneOpsjoner?.track) velgSpor(apneOpsjoner.track);
1425
1461
  }, [synlig]);
1426
1462
  const hentKort = useCallback2(async () => {
@@ -1623,8 +1659,8 @@ function Widget({
1623
1659
  spor,
1624
1660
  verdi,
1625
1661
  onEndre: endreTekst,
1626
- minTextLength: client.settings.minTextLength,
1627
- textMaxLength: client.settings.textMaxLength,
1662
+ minTextLength: innstillinger.minTextLength,
1663
+ textMaxLength: innstillinger.textMaxLength,
1628
1664
  kontekst: client.context,
1629
1665
  fjernedeFelter: fjernede,
1630
1666
  onFjernKontekst: (f) => setFjernede((x) => [...x, f]),
@@ -1754,6 +1790,309 @@ function InnspelPrivate({ children, style }) {
1754
1790
  return /* @__PURE__ */ jsx10(View10, { ref, collapsable: false, style, testID: "innspel-privat", children });
1755
1791
  }
1756
1792
 
1793
+ // src/program/InnspelProgram.tsx
1794
+ import { InnspelApiError as InnspelApiError2 } from "@innspel/core";
1795
+ import { useCallback as useCallback4, useEffect as useEffect3, useState as useState5 } from "react";
1796
+ import { Modal as Modal3, Pressable as Pressable7, Text as Text10, View as View12 } from "react-native";
1797
+
1798
+ // src/program/Aksept.tsx
1799
+ import "react";
1800
+ import { Linking, Modal as Modal2, Pressable as Pressable6, ScrollView as ScrollView2, Text as Text9, View as View11 } from "react-native";
1801
+
1802
+ // src/program/periode.ts
1803
+ var MANEDER = [
1804
+ "januar",
1805
+ "februar",
1806
+ "mars",
1807
+ "april",
1808
+ "mai",
1809
+ "juni",
1810
+ "juli",
1811
+ "august",
1812
+ "september",
1813
+ "oktober",
1814
+ "november",
1815
+ "desember"
1816
+ ];
1817
+ function periode(opensAt, closesAt) {
1818
+ const fra = new Date(opensAt);
1819
+ const til = new Date(closesAt);
1820
+ if (Number.isNaN(fra.getTime()) || Number.isNaN(til.getTime())) return "";
1821
+ const sammeManed = fra.getMonth() === til.getMonth() && fra.getFullYear() === til.getFullYear();
1822
+ return sammeManed ? `${fra.getDate()}.\u2013${til.getDate()}. ${MANEDER[til.getMonth()]}` : `${fra.getDate()}. ${MANEDER[fra.getMonth()]} \u2013 ${til.getDate()}. ${MANEDER[til.getMonth()]}`;
1823
+ }
1824
+ function minutterPerOppgave(tasks) {
1825
+ if (tasks.length === 0) return 0;
1826
+ return Math.round(tasks.reduce((sum, t2) => sum + t2.estimatedMinutes, 0) / tasks.length);
1827
+ }
1828
+
1829
+ // src/program/Aksept.tsx
1830
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1831
+ function Aksept({
1832
+ program,
1833
+ harToken,
1834
+ sender,
1835
+ feil,
1836
+ onBliMed,
1837
+ onLukk
1838
+ }) {
1839
+ const tema = useTema();
1840
+ const tall = tekst.akseptTall(
1841
+ program.tasks.length,
1842
+ minutterPerOppgave(program.tasks),
1843
+ periode(program.opensAt, program.closesAt),
1844
+ program.maxContacts
1845
+ );
1846
+ return /* @__PURE__ */ jsx11(Modal2, { visible: true, transparent: true, animationType: "slide", onRequestClose: onLukk, accessibilityViewIsModal: true, children: /* @__PURE__ */ jsx11(View11, { style: { flex: 1, backgroundColor: tema.color.scrim, justifyContent: "flex-end" }, children: /* @__PURE__ */ jsxs10(
1847
+ View11,
1848
+ {
1849
+ testID: "program-aksept",
1850
+ accessibilityViewIsModal: true,
1851
+ style: {
1852
+ backgroundColor: tema.color.bg,
1853
+ borderTopLeftRadius: tema.radius.lg * 2,
1854
+ borderTopRightRadius: tema.radius.lg * 2,
1855
+ padding: tema.screenPadding,
1856
+ gap: tema.space.md,
1857
+ maxHeight: "92%",
1858
+ minHeight: "60%"
1859
+ },
1860
+ children: [
1861
+ /* @__PURE__ */ jsxs10(View11, { style: { flexDirection: "row", justifyContent: "space-between", alignItems: "center" }, children: [
1862
+ /* @__PURE__ */ jsx11(Btn, { variant: "ghost", testID: "aksept-tilbake", ikon: "\u2190", label: tekst.tilbake, onPress: onLukk }),
1863
+ /* @__PURE__ */ jsx11(View11, {})
1864
+ ] }),
1865
+ feil ? /* @__PURE__ */ jsx11(Banner, { live: true, ikon: "\u26A0", tone: "danger", children: tekst.akseptFeil(feil) }) : null,
1866
+ /* @__PURE__ */ jsxs10(ScrollView2, { contentContainerStyle: { gap: tema.space.md }, children: [
1867
+ /* @__PURE__ */ jsx11(H, { sub: tekst.akseptHva, children: program.name }),
1868
+ /* @__PURE__ */ jsx11(
1869
+ Text9,
1870
+ {
1871
+ accessibilityRole: "text",
1872
+ style: {
1873
+ color: tema.color.textPrimary,
1874
+ fontSize: tema.font.body,
1875
+ lineHeight: tema.lineHeight.body,
1876
+ fontWeight: tema.weight.semibold
1877
+ },
1878
+ children: tall
1879
+ }
1880
+ ),
1881
+ /* @__PURE__ */ jsx11(Text9, { style: { color: tema.color.textPrimary, fontSize: tema.font.body, lineHeight: tema.lineHeight.body }, children: program.hypothesis }),
1882
+ /* @__PURE__ */ jsxs10(Card, { children: [
1883
+ /* @__PURE__ */ jsx11(H, { children: tekst.akseptOppgaver }),
1884
+ program.tasks.map((o) => /* @__PURE__ */ jsxs10(View11, { style: { gap: tema.space.xs }, children: [
1885
+ /* @__PURE__ */ jsx11(Text9, { style: { color: tema.color.textPrimary, fontSize: tema.font.body, lineHeight: tema.lineHeight.body }, children: o.title }),
1886
+ /* @__PURE__ */ jsxs10(Hint, { children: [
1887
+ tekst.akseptOppgaveTid(o.estimatedMinutes),
1888
+ o.screenHint ? ` \xB7 ${o.screenHint}` : ""
1889
+ ] })
1890
+ ] }, o.id))
1891
+ ] }),
1892
+ program.knownIssues.length > 0 ? /* @__PURE__ */ jsxs10(Card, { children: [
1893
+ /* @__PURE__ */ jsx11(H, { sub: tekst.akseptKjenteFeilUnder, children: tekst.akseptKjenteFeil(program.knownIssues.length) }),
1894
+ program.knownIssues.map((k) => /* @__PURE__ */ jsxs10(View11, { style: { gap: tema.space.xs }, children: [
1895
+ /* @__PURE__ */ jsx11(Text9, { style: { color: tema.color.textPrimary, fontSize: tema.font.small, lineHeight: tema.lineHeight.small }, children: k.title }),
1896
+ k.workaround ? /* @__PURE__ */ jsxs10(Text9, { style: { color: tema.color.textSecondary, fontSize: tema.font.small, lineHeight: tema.lineHeight.small }, children: [
1897
+ tekst.omvei,
1898
+ " ",
1899
+ k.workaround
1900
+ ] }) : null,
1901
+ k.fixedIn ? /* @__PURE__ */ jsx11(
1902
+ StatusPill,
1903
+ {
1904
+ ikon: k.status === "fikset" ? "\u2713" : "\u{1F527}",
1905
+ etikett: k.status === "fikset" ? tekst.rettetI(k.fixedIn) : tekst.rettesI(k.fixedIn),
1906
+ farge: k.status === "fikset" ? "success" : "info"
1907
+ }
1908
+ ) : null
1909
+ ] }, k.id))
1910
+ ] }) : null,
1911
+ /* @__PURE__ */ jsxs10(Card, { children: [
1912
+ /* @__PURE__ */ jsx11(H, { sub: tekst.akseptInnsamlingTekst, children: tekst.akseptInnsamling }),
1913
+ /* @__PURE__ */ jsx11(Hint, { children: tekst.kontekstAldri })
1914
+ ] }),
1915
+ program.termsUrl && !program.commitmentFree ? /* @__PURE__ */ jsxs10(View11, { style: { gap: tema.space.xs }, children: [
1916
+ /* @__PURE__ */ jsx11(
1917
+ Pressable6,
1918
+ {
1919
+ testID: "vilkar-lenke",
1920
+ accessibilityRole: "link",
1921
+ accessibilityLabel: tekst.akseptVilkar,
1922
+ onPress: () => void Linking.openURL(String(program.termsUrl)),
1923
+ style: { minHeight: tema.touch, justifyContent: "center" },
1924
+ children: /* @__PURE__ */ jsxs10(Text9, { style: { color: tema.color.textPrimary, fontSize: tema.font.body, textDecorationLine: "underline" }, children: [
1925
+ "\u{1F517} ",
1926
+ tekst.akseptVilkar
1927
+ ] })
1928
+ }
1929
+ ),
1930
+ /* @__PURE__ */ jsx11(Hint, { children: tekst.akseptVilkarHint })
1931
+ ] }) : /* @__PURE__ */ jsx11(Hint, { children: tekst.akseptIngenVilkar }),
1932
+ /* @__PURE__ */ jsxs10(Text9, { style: { color: tema.color.textPrimary, fontSize: tema.font.body, lineHeight: tema.lineHeight.body }, children: [
1933
+ "\u{1F6AA} ",
1934
+ tekst.akseptUtgang
1935
+ ] })
1936
+ ] }),
1937
+ /* @__PURE__ */ jsx11(
1938
+ Btn,
1939
+ {
1940
+ testID: "bli-med",
1941
+ label: tekst.bliMed,
1942
+ disabled: !harToken || sender,
1943
+ hint: harToken ? tekst.bliMedHint : tekst.bliMedUtenToken,
1944
+ onPress: onBliMed
1945
+ }
1946
+ )
1947
+ ]
1948
+ }
1949
+ ) }) });
1950
+ }
1951
+
1952
+ // src/program/InnspelProgram.tsx
1953
+ import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1954
+ function InnspelProgram({
1955
+ invitationToken,
1956
+ onEndring
1957
+ }) {
1958
+ const { client } = useInnspel();
1959
+ const tema = useTema();
1960
+ const [mine, setMine] = useState5();
1961
+ const [deltakelse, setDeltakelse] = useState5();
1962
+ const [viserAksept, setViserAksept] = useState5(false);
1963
+ const [viserGaaUt, setViserGaaUt] = useState5(false);
1964
+ const [avvist, setAvvist] = useState5(false);
1965
+ const [sender, setSender] = useState5(false);
1966
+ const [feil, setFeil] = useState5();
1967
+ useEffect3(() => {
1968
+ if (client.isAnonymous) return;
1969
+ let levende = true;
1970
+ void client.programs.list().then((svar) => {
1971
+ if (!levende) return;
1972
+ setMine(svar);
1973
+ setDeltakelse(svar.participations.find((p) => p.status !== "trukket"));
1974
+ }).catch(() => void 0);
1975
+ return () => {
1976
+ levende = false;
1977
+ };
1978
+ }, [client]);
1979
+ const bliMed = useCallback4(async () => {
1980
+ if (!invitationToken) return;
1981
+ setSender(true);
1982
+ setFeil(void 0);
1983
+ try {
1984
+ const ny = await client.programs.accept(invitationToken);
1985
+ setDeltakelse(ny);
1986
+ setViserAksept(false);
1987
+ onEndring?.(ny);
1988
+ } catch (e) {
1989
+ setFeil(e instanceof InnspelApiError2 ? e.message : "tjeneren svarte ikke.");
1990
+ } finally {
1991
+ setSender(false);
1992
+ }
1993
+ }, [client, invitationToken, onEndring]);
1994
+ const gaaUt = useCallback4(async () => {
1995
+ if (!deltakelse) return;
1996
+ setSender(true);
1997
+ try {
1998
+ await client.programs.withdraw(deltakelse.id);
1999
+ setDeltakelse(void 0);
2000
+ setViserGaaUt(false);
2001
+ onEndring?.(null);
2002
+ } catch (e) {
2003
+ setFeil(e instanceof InnspelApiError2 ? e.message : "tjeneren svarte ikke.");
2004
+ } finally {
2005
+ setSender(false);
2006
+ }
2007
+ }, [client, deltakelse, onEndring]);
2008
+ const invitasjon = deltakelse ? void 0 : mine?.invitations[0];
2009
+ const program = deltakelse?.program ?? invitasjon?.program;
2010
+ if (!program) return null;
2011
+ if (deltakelse) {
2012
+ return /* @__PURE__ */ jsxs11(View12, { testID: "program-deltakelse", accessibilityLabel: tekst.deltakelseRegion, style: { gap: tema.space.sm }, children: [
2013
+ /* @__PURE__ */ jsxs11(Card, { children: [
2014
+ /* @__PURE__ */ jsx12(H, { sub: tekst.deltakelsePeriode(periode(program.opensAt, program.closesAt), deltakelse.contactsSent, program.maxContacts), children: program.name }),
2015
+ /* @__PURE__ */ jsx12(Hint, { children: tekst.deltakelseOppgaverKommer }),
2016
+ /* @__PURE__ */ jsx12(Btn, { variant: "ghost", testID: "gaa-ut", ikon: "\u{1F6AA}", label: tekst.gaaUt, onPress: () => setViserGaaUt(true) })
2017
+ ] }),
2018
+ viserGaaUt ? /* @__PURE__ */ jsx12(Modal3, { visible: true, transparent: true, animationType: "fade", onRequestClose: () => setViserGaaUt(false), accessibilityViewIsModal: true, children: /* @__PURE__ */ jsx12(View12, { style: { flex: 1, backgroundColor: tema.color.scrim, justifyContent: "flex-end", padding: tema.space.md }, children: /* @__PURE__ */ jsxs11(
2019
+ View12,
2020
+ {
2021
+ testID: "gaa-ut-dialog",
2022
+ accessibilityViewIsModal: true,
2023
+ style: {
2024
+ backgroundColor: tema.color.surface,
2025
+ borderRadius: tema.radius.lg,
2026
+ borderWidth: tema.hairline,
2027
+ borderColor: tema.color.borderStrong,
2028
+ padding: tema.space.md,
2029
+ gap: tema.space.md
2030
+ },
2031
+ children: [
2032
+ /* @__PURE__ */ jsx12(H, { children: tekst.gaaUtTittel }),
2033
+ /* @__PURE__ */ jsx12(Text10, { style: { color: tema.color.textPrimary, fontSize: tema.font.body, lineHeight: tema.lineHeight.body }, children: tekst.gaaUtTekst(program.name) }),
2034
+ /* @__PURE__ */ jsxs11(View12, { style: { flexDirection: "row", gap: tema.space.sm }, children: [
2035
+ /* @__PURE__ */ jsx12(Btn, { grow: true, variant: "secondary", testID: "bli-vaerende", label: tekst.bliVaerende, onPress: () => setViserGaaUt(false) }),
2036
+ /* @__PURE__ */ jsx12(Btn, { grow: true, variant: "danger", testID: "bekreft-gaa-ut", ikon: "\u{1F6AA}", label: tekst.gaaUtBekreft, disabled: sender, onPress: () => void gaaUt() })
2037
+ ] })
2038
+ ]
2039
+ }
2040
+ ) }) }) : null
2041
+ ] });
2042
+ }
2043
+ if (!invitasjon || avvist) return null;
2044
+ return /* @__PURE__ */ jsxs11(Fragment2, { children: [
2045
+ /* @__PURE__ */ jsxs11(
2046
+ View12,
2047
+ {
2048
+ testID: "program-banner",
2049
+ accessibilityLabel: tekst.invitasjonRegion,
2050
+ style: {
2051
+ backgroundColor: tema.color.brandBg,
2052
+ borderRadius: tema.radius.lg,
2053
+ borderWidth: tema.hairline,
2054
+ borderColor: tema.color.brand,
2055
+ padding: tema.space.md,
2056
+ gap: tema.space.sm
2057
+ },
2058
+ children: [
2059
+ /* @__PURE__ */ jsxs11(View12, { style: { flexDirection: "row", gap: tema.space.sm, alignItems: "flex-start" }, children: [
2060
+ /* @__PURE__ */ jsx12(Text10, { accessibilityElementsHidden: true, importantForAccessibility: "no", style: { fontSize: tema.font.h3 }, children: "\u{1F9EA}" }),
2061
+ /* @__PURE__ */ jsxs11(View12, { style: { flex: 1, gap: tema.space.xs }, children: [
2062
+ /* @__PURE__ */ jsx12(Text10, { style: { color: tema.color.textPrimary, fontSize: tema.font.body, lineHeight: tema.lineHeight.body }, children: tekst.invitasjonHva(program.name) }),
2063
+ /* @__PURE__ */ jsx12(Hint, { children: tekst.invitasjonKontakter(program.maxContacts) })
2064
+ ] }),
2065
+ /* @__PURE__ */ jsx12(
2066
+ Pressable7,
2067
+ {
2068
+ testID: "avvis-invitasjon",
2069
+ accessibilityRole: "button",
2070
+ accessibilityLabel: tekst.avvisInvitasjon,
2071
+ onPress: () => setAvvist(true),
2072
+ style: { minWidth: tema.touch, minHeight: tema.touch, alignItems: "center", justifyContent: "center" },
2073
+ children: /* @__PURE__ */ jsx12(Text10, { style: { color: tema.color.textSecondary, fontSize: tema.font.body }, children: "\u2715" })
2074
+ }
2075
+ )
2076
+ ] }),
2077
+ feil && !viserAksept ? /* @__PURE__ */ jsx12(Banner, { live: true, ikon: "\u26A0", tone: "danger", children: tekst.akseptFeil(feil) }) : null,
2078
+ /* @__PURE__ */ jsx12(Btn, { variant: "secondary", testID: "les-mer", label: tekst.lesMer, onPress: () => setViserAksept(true) })
2079
+ ]
2080
+ }
2081
+ ),
2082
+ viserAksept ? /* @__PURE__ */ jsx12(
2083
+ Aksept,
2084
+ {
2085
+ program,
2086
+ harToken: !!invitationToken,
2087
+ sender,
2088
+ ...feil ? { feil } : {},
2089
+ onBliMed: () => void bliMed(),
2090
+ onLukk: () => setViserAksept(false)
2091
+ }
2092
+ ) : null
2093
+ ] });
2094
+ }
2095
+
1757
2096
  // src/lagring.ts
1758
2097
  function createStorage(pakke) {
1759
2098
  return {
@@ -1776,6 +2115,7 @@ function createMemoryStorage() {
1776
2115
  }
1777
2116
  export {
1778
2117
  InnspelPrivate,
2118
+ InnspelProgram,
1779
2119
  InnspelProvider,
1780
2120
  byggTema,
1781
2121
  createMemoryStorage,