@@ -0,0 +1,367 @@
1+module Pages.EditCipher exposing (..)
2+
3+import Bridge
4+import Html exposing (..)
5+import Html.Attributes as Attr
6+import Html.Events as Ev
7+import Page exposing (..)
8+import Utils exposing (..)
9+
10+
11+type alias Model =
12+ { fullCipher : Bridge.FullCipher
13+ }
14+
15+
16+type Msg
17+ = EditCipher Bridge.FullCipher
18+
19+
20+type alias Callbacks msg =
21+ { save : Bridge.FullCipher -> msg }
22+
23+
24+page : Callbacks emsg -> Page Bridge.FullCipher Model Msg emsg
25+page callbacks liftMsg =
26+ { init = \cipher -> Tuple.mapSecond (Cmd.map liftMsg) (init cipher)
27+ , view = \model -> view model |> List.map (Html.map liftMsg)
28+ , update = \msg model -> update callbacks liftMsg msg model
29+ , subscriptions = \model -> subscriptions model |> Sub.map liftMsg
30+ , title =
31+ \{ fullCipher } ->
32+ [ text fullCipher.name
33+ , span [ Attr.class "u-float-right" ] [ iconButton "task-outstanding" (callbacks.save fullCipher) ]
34+ ]
35+ }
36+
37+
38+init : Bridge.FullCipher -> ( Model, Cmd Msg )
39+init cipher =
40+ ( { fullCipher = cipher
41+ }
42+ , Cmd.none
43+ )
44+
45+
46+update : Callbacks emsg -> (Msg -> emsg) -> Msg -> Model -> ( Result String Model, Cmd emsg )
47+update _ _ msg model =
48+ case msg of
49+ EditCipher fullCipher ->
50+ ( Ok { model | fullCipher = fullCipher }, Cmd.none )
51+
52+
53+subscriptions : Model -> Sub Msg
54+subscriptions _ =
55+ Sub.none
56+
57+
58+heading : String -> String -> Html msg
59+heading icons name =
60+ span [ Attr.class "u-text--muted" ] [ i [ Attr.class ("p-icon--" ++ icons) ] [], text (" " ++ name) ]
61+
62+
63+row : { name : String, nameIcon : String, attrs : List (Html.Attribute msg) } -> Html msg
64+row { name, nameIcon, attrs } =
65+ p []
66+ [ heading nameIcon name
67+ , br [] []
68+ , input attrs []
69+ ]
70+
71+
72+view : Model -> List (Html Msg)
73+view { fullCipher } =
74+ case fullCipher.cipher of
75+ Bridge.LoginCipher cipher ->
76+ let
77+ { username, password, uris } =
78+ cipher
79+
80+ edit c =
81+ { fullCipher | cipher = Bridge.LoginCipher c } |> EditCipher
82+ in
83+ [ row
84+ { name = "Username"
85+ , nameIcon = "user"
86+ , attrs =
87+ [ Attr.type_ "text"
88+ , Attr.value (Maybe.withDefault "" username)
89+ , Ev.onInput (\x -> edit { cipher | username = Just x })
90+ , Attr.attribute "autocomplete" "username"
91+ ]
92+ }
93+ , row
94+ { name = "Password"
95+ , nameIcon = "security"
96+ , attrs =
97+ [ Attr.type_ "text"
98+ , Attr.value (Maybe.withDefault "" password)
99+ , Ev.onInput (\x -> edit { cipher | password = Just x })
100+ , Attr.attribute "autocomplete" "new-password"
101+ ]
102+ }
103+ ]
104+ ++ (uris
105+ |> List.indexedMap
106+ (\i uri ->
107+ row
108+ { name = "URL"
109+ , nameIcon = "get-link"
110+ , attrs =
111+ [ Attr.type_ "url"
112+ , Attr.value uri
113+ , Ev.onInput (\x -> edit { cipher | uris = replace i x uris })
114+ ]
115+ }
116+ )
117+ )
118+ ++ [ row
119+ { name = "URL"
120+ , nameIcon = "get-link"
121+ , attrs =
122+ [ Attr.type_ "url"
123+ , Ev.onInput (\x -> edit { cipher | uris = uris ++ [ x ] })
124+ ]
125+ }
126+ ]
127+
128+ Bridge.NoteCipher contents ->
129+ [ p []
130+ [ heading "switcher-environments" "Note"
131+ , br [] []
132+ , textarea
133+ [ Attr.spellcheck True
134+ , Attr.style "height" "70vh"
135+ , Attr.value contents
136+ , Ev.onInput (\x -> EditCipher { fullCipher | cipher = Bridge.NoteCipher x })
137+ ]
138+ []
139+ ]
140+ ]
141+
142+ Bridge.CardCipher cipher ->
143+ let
144+ { number, code, cardholderName, expMonth, expYear } =
145+ cipher
146+
147+ edit c =
148+ { fullCipher | cipher = Bridge.CardCipher c } |> EditCipher
149+ in
150+ [ row
151+ { name = "Card number"
152+ , nameIcon = "containers"
153+ , attrs =
154+ [ Attr.attribute "autocomplete" "cc-number"
155+ , Attr.type_ "text"
156+ , Attr.value (Maybe.withDefault "" number)
157+ , Ev.onInput (\x -> edit { cipher | number = Just x })
158+ ]
159+ }
160+ , p []
161+ [ heading "timed-out" "Expiration date"
162+ , br [] []
163+ , input
164+ [ Attr.attribute "autocomplete" "cc-exp-month"
165+ , Attr.type_ "text"
166+ , Attr.value (Maybe.withDefault "" expMonth)
167+ , Ev.onInput (\x -> edit { cipher | expMonth = Just x })
168+ , Attr.style "width" "20%"
169+ , Attr.class "u-align--right"
170+ ]
171+ []
172+ , text " / "
173+ , input
174+ [ Attr.attribute "autocomplete" "cc-exp-year"
175+ , Attr.type_ "text"
176+ , Attr.value (Maybe.withDefault "" expYear)
177+ , Ev.onInput (\x -> edit { cipher | expYear = Just x })
178+ , Attr.style "width" "20%"
179+ ]
180+ []
181+ ]
182+ , row
183+ { name = "CVV"
184+ , nameIcon = "security"
185+ , attrs =
186+ [ Attr.attribute "autocomplete" "cc-csc"
187+ , Attr.type_ "text"
188+ , Attr.value (Maybe.withDefault "" code)
189+ , Ev.onInput (\x -> edit { cipher | code = Just x })
190+ ]
191+ }
192+ , row
193+ { name = "Cardholder name"
194+ , nameIcon = "user"
195+ , attrs =
196+ [ Attr.attribute "autocomplete" "cc-name"
197+ , Attr.type_ "text"
198+ , Attr.value (Maybe.withDefault "" cardholderName)
199+ , Ev.onInput (\x -> edit { cipher | cardholderName = Just x })
200+ ]
201+ }
202+ ]
203+
204+ Bridge.IdentityCipher cipher ->
205+ let
206+ { address1, address2, address3, company, email, firstName, lastName, licenseNumber, middleName, passportNumber, phone, ssn, username } =
207+ cipher
208+
209+ edit c =
210+ { fullCipher | cipher = Bridge.IdentityCipher c } |> EditCipher
211+ in
212+ [ row
213+ { name = "First name"
214+ , nameIcon = ""
215+ , attrs =
216+ [ Attr.attribute "autocomplete" "given-name"
217+ , Attr.type_ "text"
218+ , Attr.value (Maybe.withDefault "" firstName)
219+ , Ev.onInput (\x -> edit { cipher | firstName = Just x })
220+ ]
221+ }
222+ , row
223+ { name = "Middle name"
224+ , nameIcon = ""
225+ , attrs =
226+ [ Attr.attribute "autocomplete" "additional-name"
227+ , Attr.type_ "text"
228+ , Attr.value (Maybe.withDefault "" middleName)
229+ , Ev.onInput (\x -> edit { cipher | middleName = Just x })
230+ ]
231+ }
232+ , row
233+ { name = "Last name"
234+ , nameIcon = ""
235+ , attrs =
236+ [ Attr.attribute "autocomplete" "family-name"
237+ , Attr.type_ "text"
238+ , Attr.value (Maybe.withDefault "" lastName)
239+ , Ev.onInput (\x -> edit { cipher | lastName = Just x })
240+ ]
241+ }
242+ , row
243+ { name = "Username"
244+ , nameIcon = ""
245+ , attrs =
246+ [ Attr.attribute "autocomplete" "username"
247+ , Attr.type_ "text"
248+ , Attr.value (Maybe.withDefault "" username)
249+ , Ev.onInput (\x -> edit { cipher | username = Just x })
250+ ]
251+ }
252+ , row
253+ { name = "Company"
254+ , nameIcon = ""
255+ , attrs =
256+ [ Attr.attribute "autocomplete" "organization"
257+ , Attr.type_ "text"
258+ , Attr.value (Maybe.withDefault "" company)
259+ , Ev.onInput (\x -> edit { cipher | company = Just x })
260+ ]
261+ }
262+ , row
263+ { name = "Social security number"
264+ , nameIcon = ""
265+ , attrs =
266+ [ Attr.attribute "autocomplete" "off"
267+ , Attr.type_ "number"
268+ , Attr.value (Maybe.withDefault "" ssn)
269+ , Ev.onInput (\x -> edit { cipher | ssn = Just x })
270+ ]
271+ }
272+ , row
273+ { name = "Passport number"
274+ , nameIcon = ""
275+ , attrs =
276+ [ Attr.attribute "autocomplete" "off"
277+ , Attr.type_ "number"
278+ , Attr.value (Maybe.withDefault "" passportNumber)
279+ , Ev.onInput (\x -> edit { cipher | passportNumber = Just x })
280+ ]
281+ }
282+ , row
283+ { name = "License Number"
284+ , nameIcon = ""
285+ , attrs =
286+ [ Attr.attribute "autocomplete" "off"
287+ , Attr.type_ "number"
288+ , Attr.value (Maybe.withDefault "" licenseNumber)
289+ , Ev.onInput (\x -> edit { cipher | licenseNumber = Just x })
290+ ]
291+ }
292+ , row
293+ { name = "Email"
294+ , nameIcon = ""
295+ , attrs =
296+ [ Attr.attribute "autocomplete" "email"
297+ , Attr.type_ "email"
298+ , Attr.value (Maybe.withDefault "" email)
299+ , Ev.onInput (\x -> edit { cipher | email = Just x })
300+ ]
301+ }
302+ , row
303+ { name = "Phone number"
304+ , nameIcon = ""
305+ , attrs =
306+ [ Attr.attribute "autocomplete" "tel"
307+ , Attr.type_ "tel"
308+ , Attr.value (Maybe.withDefault "" phone)
309+ , Ev.onInput (\x -> edit { cipher | phone = Just x })
310+ ]
311+ }
312+ , row
313+ { name = "Address line 1"
314+ , nameIcon = ""
315+ , attrs =
316+ [ Attr.attribute "autocomplete" "address-line1"
317+ , Attr.type_ "text"
318+ , Attr.value (Maybe.withDefault "" address1)
319+ , Ev.onInput (\x -> edit { cipher | address1 = Just x })
320+ ]
321+ }
322+ , row
323+ { name = "Address line 2"
324+ , nameIcon = ""
325+ , attrs =
326+ [ Attr.attribute "autocomplete" "address-line2"
327+ , Attr.type_ "text"
328+ , Attr.value (Maybe.withDefault "" address2)
329+ , Ev.onInput (\x -> edit { cipher | address2 = Just x })
330+ ]
331+ }
332+ , row
333+ { name = "Address line 3"
334+ , nameIcon = ""
335+ , attrs =
336+ [ Attr.attribute "autocomplete" "address-line3"
337+ , Attr.type_ "text"
338+ , Attr.value (Maybe.withDefault "" address3)
339+ , Ev.onInput (\x -> edit { cipher | address3 = Just x })
340+ ]
341+ }
342+ ]
343+
344+
345+hiddenPassword : String
346+hiddenPassword =
347+ String.repeat 12 "●"
348+
349+
350+hiddenCvv : String
351+hiddenCvv =
352+ String.repeat 3 "●"
353+
354+
355+iconButton : String -> msg -> Html msg
356+iconButton name msg =
357+ button [ Attr.class "p-button is-inline is-dense has-icon", Ev.onClick msg ]
358+ [ i [ Attr.class ("p-icon--" ++ name) ] [] ]
359+
360+
361+hiddenButtonIcon : Bool -> String
362+hiddenButtonIcon hidden =
363+ if hidden then
364+ "show"
365+
366+ else
367+ "hide"